Async/Await in JavaScript

Why async/await was introduced
Introduced in ES2017, async/await is a modern syntax in JavaScript designed to simplify working with Promises. It allows you to write asynchronous code that looks and behaves like synchronous code, making it significantly more readable and easier to maintain.
Async/await was introduced to simplify asynchronous programming by allowing non-blocking, asynchronous code to be written and read like traditional synchronous code. It solves complex issues—such as "[callback hell]" and deeply nested .then() promise chains—making code more readable, maintainable, and easier to debug.
Key Components
asyncKeyword: Declares an asynchronous function. These functions always return a Promise. If the function returns a non-promise value, JavaScript automatically wraps it in a resolved Promise.awaitKeyword: Can only be used inside anasyncfunction. It pauses the execution of the function until the Promise settles (either resolves or rejects). Once settled, it returns the resolved value or throws the rejected error.Error Handling: Instead of using
.catch()chains, you can use standard try...catch blocks within theasyncfunction to handle errors more cleanly.
Execution Flow
When an
asyncfunction is called, it executes synchronously until it reaches the firstawaitexpression.At the
await, progress through that specific function is suspended, and control is yielded back to the caller while the promise settles.The JavaScript engine continues executing other tasks (like handling events or other scripts) in the meantime, ensuring the application remains responsive.
Once the promise resolves, the
asyncfunction resumes execution from where it left off.
Practical Benefits
Readability: Eliminates "callback hell" and long
.then()chains, making the flow of logic easier to follow.Parallel Execution: While
awaitis sequential by default, you can initiate multiple promises simultaneously and then use Promise.all() to wait for all of them to resolve concurrently.Maintainability: Debugging is easier because the call stack for errors is more representative of the code's logical flow.
How async functions work
JavaScript relies heavily on asynchronous programming to handle tasks like API calls, timers, and file operations without blocking execution. One of the cleanest ways to work with async code is using async and await.
🔹 What is an Async Function?
An async function is a function that always returns a Promise.
async function greet() {
return "Hello";
}
👉 Even though it returns a string, JavaScript wraps it inside a Promise.
🔹 What is await?
The await keyword is used inside async functions to pause execution until a Promise is resolved.
async function getData() {
let response = await fetch("https://api.example.com");
let data = await response.json();
console.log(data);
}
await makes async code look like synchronous code
🔹 How It Works Behind the Scenes
JavaScript is single-threaded
Async tasks are handled by:
Web APIs
Callback queue
Event loop
When await is used:
Function pauses
Other code continues
Once Promise resolves → execution resumes
🔹 Example (Step-by-step)
async function example() {
console.log("Start");
await new Promise(resolve => setTimeout(resolve, 2000));
console.log("End");
}
example();
console.log("Outside");
Output:
Start
Outside
End
👉 Shows non-blocking behavior
🔹 Error Handling in Async Functions
Use try...catch:
async function fetchData() {
try {
let res = await fetch("wrong-url");
let data = await res.json();
} catch (error) {
console.log("Error:", error.message);
}
}
🔹 Why Use Async/Await?
Cleaner and readable code
Easier than callbacks
Better error handling
Avoids “callback hell”
🔹 Important Points
async→ makes function return Promiseawait→ pauses executionWorks only inside async functions
Makes code look synchronous but runs asynchronously
🔹 Conclusion
Async functions simplify asynchronous programming by allowing developers to write clean, readable, and maintainable code. By combining async and await, you can handle complex operations like API calls with ease.
Await keyword concept
The await keyword pauses the execution of an async function, waiting for a Promise (or similar task) to settle—either resolve or reject—before resuming. It enables asynchronous, non-blocking code to be written in a synchronous-looking style. When paused, it yields control back to the caller, allowing other tasks to run.
Key Concepts of await
Context: It can only be used inside a function marked as
asyncin languages like JavaScript, Python, and C#.Non-Blocking Behavior:
awaitdoes not pause the entire application's execution thread, only the specificasyncfunction it is within.Unwrapping Promises: It extracts the fulfilled value from a Promise, making it easy to assign the result directly to a variable (e.g.,
let result = await promise;).Error Handling: If the promise is rejected,
awaitthrows the exception, which can be caught using traditionaltry/catchblocks.
Why Use await?
It improves code readability by avoiding complex Promise chaining (e.g., .then().catch()) and allows for sequential-looking code for asynchronous operations like fetching data, reading files, or database queries.
Error handling with async code
Asynchronous code makes applications fast and responsive—but it also introduces new ways for errors to occur. Handling these errors properly is essential to build reliable and user-friendly applications.
🔹 Why Error Handling Matters in Async Code
When working with:
API calls
Promises
Timers
Errors may not appear immediately. Without handling them, your app may:
Fail silently ❌
Crash unexpectedly ❌
🔹 1. Using try...catch with async/await
The most common and clean way to handle async errors:
async function fetchData() {
try {
let response = await fetch("https://api.example.com");
let data = await response.json();
console.log(data);
} catch (error) {
console.log("Error:", error.message);
}
}
try handles any error thrown during await
🔹 2. Handling Errors with Promises (.catch())
If you’re not using async/await, use .catch():
fetch("https://api.example.com")
.then(res => res.json())
.then(data => console.log(data))
.catch(error => console.log("Error:", error));
Catches rejected promises
🔹 3. Throwing Errors in Async Functions
You can throw custom errors inside async functions:
async function login(password) {
if (password.length < 6) {
throw new Error("Password too short");
}
return "Success";
}
async function main() {
try {
await login("123");
} catch (e) {
console.log(e.message);
}
}
🔹 4. Important Note
await only catches rejected promises, not logical mistakes
async function test() {
try {
let data = await Promise.reject("Failed");
} catch (e) {
console.log("Caught:", e);
}
}
🔹 5. Using finally with Async Code
async function example() {
try {
await fetch("url");
} catch (e) {
console.log("Error");
} finally {
console.log("Always runs");
}
}
Useful for cleanup tasks
🔹 Best Practices
Always wrap
awaitintry...catchUse meaningful error messages
Don’t ignore errors
Handle errors at the right level
🔹 Conclusion
Error handling in async code ensures your application remains stable and predictable. Whether you use try...catch or .catch(), the goal is to gracefully handle failures without breaking the app.
Comparison with promises
When working with asynchronous code in JavaScript, you’ll commonly use Promises or async/await. Both solve the same problem—but in different ways.
🔹 What are Promises?
A Promise represents a value that may be available now, later, or never.
fetch("https://api.example.com")
.then(res => res.json())
.then(data => console.log(data))
.catch(err => console.log(err));
Uses .then() and .catch() chaining
🔹 What is Async/Await?
async/await is a cleaner way to work with Promises.
async function getData() {
try {
let res = await fetch("https://api.example.com");
let data = await res.json();
console.log(data);
} catch (err) {
console.log(err);
}
}
Makes async code look synchronous
🔹 Key Differences
| Feature | Promises | Async/Await |
|---|---|---|
| Syntax | .then().catch() |
async/await |
| Readability | Less readable (chaining) | More readable |
| Error Handling | .catch() |
try...catch |
| Debugging | Harder | Easier |
| Flow Control | Chaining | Looks synchronous |
🔹 Example Comparison
Using Promises:
fetch("url")
.then(res => res.json())
.then(data => console.log(data))
.catch(err => console.log(err));
Using Async/Await:
async function fetchData() {
try {
let res = await fetch("url");
let data = await res.json();
console.log(data);
} catch (err) {
console.log(err);
}
}
🔹 When to Use What?
Use Promises when:
You need simple chaining
Working with existing
.then()code
Use Async/Await when:
You want cleaner, readable code
Handling multiple async operations
Writing modern JavaScript
🔹 Important Note
async/await is built on top of Promises
It does not replace them—it simplifies them
🔹 Conclusion
Both Promises and async/await are powerful tools for handling asynchronous operations. However, async/await is generally preferred because it:
Improves readability
Simplifies error handling
Makes code easier to




