JavaScript async and await make Promise-based code easier to read.
A website may need to wait for product data, user details, form responses, or several API requests. Promises handle this asynchronous work, while async/await gives you a cleaner way to write the same workflow.
Instead of chaining several .then() calls, you can often write asynchronous code in a top-to-bottom style that is easier to follow.
Course Home: JavaScript Tutorial for Beginners
Previous Lesson: JavaScript Promises Explained
Next Lesson: JavaScript Fetch API Explained
Quick Answer
An async function always returns a Promise.
Example:
async function getMessage() {
return "Hello";
}
Call it:
getMessage().then((message) => {
console.log(message);
});
Output:
Hello
Inside an async function, await pauses that function until a Promise settles.
Example:
async function loadData() {
const result = await Promise.resolve("Data loaded");
console.log(result);
}
loadData();
Output:
Data loaded
For errors, use:
try {
// await Promise
} catch (error) {
// handle error
}
What Is async/await in JavaScript?
async/await is syntax built on top of Promises.
It does not replace Promises.
It gives you another way to work with them.
Promise chain:
getUser()
.then((user) => {
return getOrders(user);
})
.then((orders) => {
console.log(orders);
})
.catch((error) => {
console.error(error);
});
The same workflow can often be written:
async function loadOrders() {
try {
const user = await getUser();
const orders = await getOrders(user);
console.log(orders);
} catch (error) {
console.error(error);
}
}
The second version reads more like normal step-by-step code.
Why Learn Promises Before async/await?
async/await uses Promises underneath.
When you write:
await getUser();
the value you are waiting for is normally a Promise.
Understanding JavaScript Promises helps you understand:
- What
awaitis waiting for - Why
asyncfunctions return Promises - How errors become rejected Promises
- Why
Promise.all()still matters - Why some asynchronous tasks should run together
If Promises still feel unclear, review the previous lesson first.
What Does async Do?
Add:
async
before a function declaration:
async function getUser() {
return {
name: "Riya"
};
}
Calling the function returns a Promise.
const result = getUser();
console.log(result);
result is a Promise.
You can handle it with:
getUser().then((user) => {
console.log(user.name);
});
Output:
Riya
async Functions Always Return Promises
Consider:
async function getNumber() {
return 10;
}
It looks like the function returns a normal number.
But JavaScript wraps that value in a fulfilled Promise.
This:
getNumber()
behaves like:
Promise.resolve(10)
You can use:
getNumber().then((number) => {
console.log(number);
});
Output:
10
async Function With No return
Example:
async function runTask() {
console.log("Task running");
}
The function still returns a Promise.
Its fulfilled value is:
undefined
This is similar to a normal function that does not explicitly return a value.
async Function Expression
You can store an async function in a variable:
const loadData = async function () {
return "Data";
};
Call:
loadData().then((result) => {
console.log(result);
});
Output:
Data
Async Arrow Function
Arrow functions can also be async.
Example:
const loadData = async () => {
return "Data loaded";
};
Call:
loadData().then((result) => {
console.log(result);
});
Output:
Data loaded
Async arrow functions are common in modern JavaScript.
What Does await Do?
await waits for a Promise inside an async function.
Example:
async function loadData() {
const result = await Promise.resolve("Ready");
console.log(result);
}
Call:
loadData();
Output:
Ready
The variable:
result
receives the Promise’s fulfilled value.
await Unwraps a Fulfilled Promise
Consider:
const promise = Promise.resolve(25);
Without await:
console.log(promise);
you have a Promise object.
Inside an async function:
async function showNumber() {
const number = await promise;
console.log(number);
}
Output:
25
await gives you the fulfilled value.
await Can Work With Non-Promise Values
This is valid:
async function example() {
const value = await 10;
console.log(value);
}
Output:
10
JavaScript treats the value as if it were already fulfilled.
However, await is mainly useful when working with Promises and Promise-like values.
Where Can You Use await?
Inside a normal function, this is invalid:
function loadData() {
const data = await getData();
}
The function is not async.
Use:
async function loadData() {
const data = await getData();
}
Modern JavaScript modules can also support top-level await in module contexts, but beginners should first learn the reliable pattern of using await inside async functions.
Simple async/await Example
Create a Promise-returning function:
function getProduct() {
return Promise.resolve({
name: "Keyboard",
price: 1500
});
}
Now:
async function showProduct() {
const product = await getProduct();
console.log(product.name);
console.log(product.price);
}
showProduct();
Output:
Keyboard
1500
This is the basic async/await pattern.
Step-by-Step async/await Flow
Consider:
async function loadProduct() {
const product = await getProduct();
console.log(product);
}
JavaScript:
- Calls
loadProduct(). - Reaches
await getProduct(). - Lets other JavaScript work continue while the Promise is pending.
- Resumes the async function after the Promise fulfills.
- Stores the fulfilled value in
product. - Runs the next line.
await does not freeze the entire browser.
It pauses progress inside that async function until the awaited result is ready.
async/await Does Not Block the Whole Page
This is important.
Consider:
console.log("First");
async function run() {
const result = await Promise.resolve("Second");
console.log(result);
}
run();
console.log("Third");
Output:
First
Third
Second
The async function pauses at await.
The rest of the current synchronous JavaScript continues.
Later, the async function resumes.
Real Website Example: Load User Data
Suppose:
function getUser() {
return Promise.resolve({
id: 10,
name: "Riya"
});
}
Use:
async function loadUser() {
const user = await getUser();
console.log(user.name);
}
loadUser();
Output:
Riya
The code is easier to read than a Promise chain for simple sequential work.
Sequential async Operations
Suppose the second task needs the first task’s result.
function getUser() {
return Promise.resolve({
id: 10,
name: "Riya"
});
}
function getOrders(userId) {
return Promise.resolve([
{
id: 101,
userId
}
]);
}
Use:
async function loadOrders() {
const user = await getUser();
const orders = await getOrders(user.id);
console.log(orders);
}
loadOrders();
The second request depends on:
user.id
so sequential await is appropriate.
Promise Chain vs async/await
Promise version:
getUser()
.then((user) => {
return getOrders(user.id);
})
.then((orders) => {
console.log(orders);
})
.catch((error) => {
console.error(error);
});
Async/await version:
async function loadOrders() {
try {
const user = await getUser();
const orders = await getOrders(user.id);
console.log(orders);
} catch (error) {
console.error(error);
}
}
Both use Promises.
Choose the style that makes the workflow easier to understand.
What Happens When an Awaited Promise Rejects?
Suppose:
function loadData() {
return Promise.reject(
new Error("Data failed")
);
}
This async function:
async function run() {
const data = await loadData();
console.log(data);
}
will reject because the awaited Promise rejects.
To handle that error inside the async function, use:
try...catch
Error Handling With try…catch
Example:
async function run() {
try {
const data = await loadData();
console.log(data);
} catch (error) {
console.log(error.message);
}
}
run();
Output:
Data failed
The catch block handles the rejected Promise.
Basic try…catch Structure
try {
// code that may fail
} catch (error) {
// handle the error
}
With async/await:
try {
const result = await somePromise();
} catch (error) {
console.error(error);
}
This is the most common async error-handling pattern.
finally With async/await
You can also use:
finally
Example:
async function loadData() {
try {
const data = await getData();
console.log(data);
} catch (error) {
console.error(error);
} finally {
console.log("Finished");
}
}
The finally block runs after success or failure.
Real Website Example: Loading Indicator
HTML:
<p id="status">Waiting...</p>
JavaScript:
const status =
document.querySelector("#status");
async function loadProducts() {
status.textContent = "Loading...";
try {
const products =
await getProducts();
status.textContent =
`${products.length} products loaded`;
} catch (error) {
status.textContent =
"Products could not be loaded";
} finally {
console.log("Request finished");
}
}
The page can display loading, success, and error states.
Throw Your Own Error
You can create an error with:
throw new Error()
Example:
async function checkResponse(response) {
if (!response.ok) {
throw new Error(
`HTTP error: ${response.status}`
);
}
return response;
}
The thrown error can be handled by a surrounding try...catch.
This becomes especially important with the Fetch API.
async Functions Reject When They Throw
Example:
async function getData() {
throw new Error("Failed");
}
The function returns a rejected Promise.
Handle it with:
getData().catch((error) => {
console.log(error.message);
});
Output:
Failed
Inside another async function:
async function run() {
try {
await getData();
} catch (error) {
console.log(error.message);
}
}
Return a Value From an async Function
Example:
async function calculate() {
return 100;
}
Use:
const promise = calculate();
The result is a Promise.
Inside another async function:
async function showResult() {
const result = await calculate();
console.log(result);
}
showResult();
Output:
100
Return an Awaited Value
These can often behave similarly:
async function getData() {
return fetchData();
}
and:
async function getData() {
return await fetchData();
}
The second form can be useful when a local try...catch needs to catch rejection before returning, but otherwise the extra await may not be necessary.
For beginners, focus on clarity and correct error handling rather than adding await automatically to every returned Promise.
Do Not Use await Without Need
Avoid:
async function getValue() {
const value = await 10;
return value;
}
This works, but there is no asynchronous operation.
A normal function would be clearer:
function getValue() {
return 10;
}
Use async/await when asynchronous work is actually involved.
Real Website Example: Fetch JSON
A common pattern is:
async function loadProducts() {
const response =
await fetch("/api/products");
const products =
await response.json();
console.log(products);
}
There are two awaited Promises:
fetch()
and:
response.json()
You will learn this pattern fully in the JavaScript Fetch API tutorial.
Check response.ok With Fetch
fetch() does not normally reject only because the server returns an HTTP status such as 404 or 500.
Check:
response.ok
Example:
async function loadProducts() {
const response =
await fetch("/api/products");
if (!response.ok) {
throw new Error(
`HTTP error: ${response.status}`
);
}
const products =
await response.json();
return products;
}
Handle it:
async function showProducts() {
try {
const products =
await loadProducts();
console.log(products);
} catch (error) {
console.error(error.message);
}
}
Real Website Example: Load Product and Render It
HTML:
<div id="product"></div>
JavaScript:
const productElement =
document.querySelector("#product");
async function loadProduct() {
try {
const response =
await fetch("/api/product/101");
if (!response.ok) {
throw new Error(
"Product could not be loaded"
);
}
const product =
await response.json();
productElement.textContent =
`${product.name} - ₹${product.price}`;
} catch (error) {
productElement.textContent =
error.message;
}
}
This combines async/await, Fetch, JSON, and DOM manipulation.
Sequential await Can Be Slower When Tasks Are Independent
Consider:
async function loadPage() {
const user =
await getUser();
const categories =
await getCategories();
console.log(user, categories);
}
If getCategories() does not depend on the user, the second operation starts only after the first finishes.
That creates unnecessary waiting.
Start Independent Promises Together
Better:
async function loadPage() {
const userPromise =
getUser();
const categoriesPromise =
getCategories();
const user =
await userPromise;
const categories =
await categoriesPromise;
console.log(user, categories);
}
Both operations begin before either await.
An even clearer option is Promise.all().
Use Promise.all() With async/await
Example:
async function loadPage() {
const [
user,
categories
] = await Promise.all([
getUser(),
getCategories()
]);
console.log(user);
console.log(categories);
}
Both Promises begin together.
await waits for the combined Promise.
Real Website Example: Load Dashboard Data
Suppose a dashboard needs:
- User data
- Notifications
- Settings
These requests are independent.
async function loadDashboard() {
try {
const [
user,
notifications,
settings
] = await Promise.all([
getUser(),
getNotifications(),
getSettings()
]);
console.log(user);
console.log(notifications);
console.log(settings);
} catch (error) {
console.error(
"Dashboard could not be loaded",
error
);
}
}
This is a common and useful async pattern.
When Not to Use Promise.all()
Do not use Promise.all() when later work depends on earlier results.
Example:
const user = await getUser();
const orders =
await getOrders(user.id);
getOrders() needs user.id.
The operations are dependent, so sequential await is correct.
Promise.allSettled() With await
If every outcome matters even when some Promises fail:
async function loadWidgets() {
const results =
await Promise.allSettled([
loadWeather(),
loadNews(),
loadMessages()
]);
console.log(results);
}
Every result includes a status such as:
fulfilled
or:
rejected
This is useful for independent page sections that can succeed or fail separately.
Promise.race() With await
Example:
async function getFirstResult() {
const result =
await Promise.race([
fastTask(),
slowTask()
]);
console.log(result);
}
The result comes from the first Promise that settles.
Promise.any() With await
Example:
async function getFirstSuccess() {
try {
const result =
await Promise.any([
firstSource(),
secondSource()
]);
console.log(result);
} catch (error) {
console.error(
"Every source failed"
);
}
}
Promise.any() fulfills with the first successful result.
Looping With await
You can use await inside a loop.
Example:
async function processUsers(users) {
for (const user of users) {
await saveUser(user);
}
}
This processes one user at a time.
That can be correct when order matters or when each step depends on the previous one.
Sequential Loop vs Parallel Work
This:
for (const id of ids) {
await loadProduct(id);
}
runs the requests one after another.
If the requests are independent, you may want:
const promises =
ids.map((id) => loadProduct(id));
const products =
await Promise.all(promises);
This starts the independent work together.
The right choice depends on the task.
Do Not Use forEach() With await Expecting It to Wait
A common beginner mistake is:
items.forEach(async (item) => {
await saveItem(item);
});
The outer code does not wait for all those async callbacks just because they contain await.
If you need sequential processing, use:
for (const item of items) {
await saveItem(item);
}
If the tasks can run together:
await Promise.all(
items.map((item) => saveItem(item))
);
This distinction is important.
Async Functions in Array Methods
Methods such as:
map()
can return arrays of Promises.
Example:
const promises =
ids.map(async (id) => {
return loadProduct(id);
});
promises is an array of Promises.
To get the final values:
const products =
await Promise.all(promises);
Do not assume map(async ...) automatically gives you an array of resolved values.
Real Website Example: Load Several Products
async function loadProducts(ids) {
const promises =
ids.map((id) => {
return fetch(
`/api/products/${id}`
).then((response) => {
if (!response.ok) {
throw new Error(
`Product ${id} failed`
);
}
return response.json();
});
});
return Promise.all(promises);
}
Or using async callbacks:
async function loadProducts(ids) {
const promises =
ids.map(async (id) => {
const response =
await fetch(
`/api/products/${id}`
);
if (!response.ok) {
throw new Error(
`Product ${id} failed`
);
}
return response.json();
});
return Promise.all(promises);
}
Async Event Handlers
Event handlers can be async.
Example:
const button =
document.querySelector("#loadButton");
button.addEventListener(
"click",
async () => {
const data =
await loadData();
console.log(data);
}
);
For real work, add error handling:
button.addEventListener(
"click",
async () => {
try {
const data =
await loadData();
console.log(data);
} catch (error) {
console.error(error);
}
}
);
Real Website Example: Async Form Submission
HTML:
<form id="contactForm">
<input
id="email"
name="email"
type="email"
required
>
<button
id="submitButton"
type="submit"
>
Send
</button>
</form>
<p id="status"></p>
JavaScript:
const contactForm =
document.querySelector("#contactForm");
const submitButton =
document.querySelector("#submitButton");
const status =
document.querySelector("#status");
contactForm.addEventListener(
"submit",
async (event) => {
event.preventDefault();
submitButton.disabled = true;
status.textContent = "Sending...";
try {
await sendForm();
status.textContent =
"Message sent";
} catch (error) {
status.textContent =
"Message could not be sent";
} finally {
submitButton.disabled = false;
}
}
);
This is a common real-world async pattern.
The actual request will be covered in the Fetch lesson.
Error Handling in One Large try Block
You can write:
async function loadPage() {
try {
const user =
await getUser();
const orders =
await getOrders(user.id);
const settings =
await getSettings();
console.log(
user,
orders,
settings
);
} catch (error) {
console.error(error);
}
}
Any rejection or thrown error inside the try block can move to the same catch.
This is convenient when the whole workflow shares one error path.
Separate Error Handling When Needed
Sometimes different steps need different messages.
Example:
async function loadPage() {
let user;
try {
user =
await getUser();
} catch (error) {
console.error(
"User could not be loaded"
);
return;
}
try {
const orders =
await getOrders(user.id);
console.log(orders);
} catch (error) {
console.error(
"Orders could not be loaded"
);
}
}
Use separate blocks when the recovery behavior is genuinely different.
Do not split every await into its own try...catch without reason.
Rethrow an Error
Sometimes a function needs to do local work and still let the caller handle the failure.
Example:
async function loadUser() {
try {
return await getUser();
} catch (error) {
console.error(
"User request failed"
);
throw error;
}
}
The error is logged locally and then thrown again.
The calling code can still catch it.
Catching an Error Changes the Flow
If you catch an error and do not throw again, the async function may continue or fulfill with another value.
Example:
async function getData() {
try {
return await loadData();
} catch (error) {
return [];
}
}
Now failure returns an empty array instead of rejecting.
This can be useful when an empty fallback is genuinely safe.
Do not hide serious errors with fallback values that make the application look successful when it is not.
Real Website Example: Safe Fallback
async function getRecommendations() {
try {
return await loadRecommendations();
} catch (error) {
console.error(error);
return [];
}
}
If recommendations are optional, an empty list may be an acceptable fallback.
For checkout, authentication, or payments, silently falling back would usually be inappropriate.
finally for UI Cleanup
A common pattern is:
async function submitOrder() {
loading.hidden = false;
try {
await sendOrder();
message.textContent =
"Order submitted";
} catch (error) {
message.textContent =
"Order failed";
} finally {
loading.hidden = true;
}
}
The loading state is cleared no matter how the operation ends.
await and setTimeout
setTimeout() itself does not return a Promise.
This does not wait:
await setTimeout(() => {
console.log("Done");
}, 1000);
To use a timer with await, wrap the timer in a Promise:
function wait(milliseconds) {
return new Promise((resolve) => {
setTimeout(
resolve,
milliseconds
);
});
}
Then:
async function run() {
await wait(1000);
console.log("Done");
}
run();
After about one second:
Done
Reusable wait() Function
A compact version is:
function wait(milliseconds) {
return new Promise((resolve) => {
setTimeout(resolve, milliseconds);
});
}
Use:
async function demo() {
console.log("Start");
await wait(1000);
console.log("One second later");
}
This is useful for learning and for certain controlled timing tasks.
Do not use artificial waits as a replacement for real completion signals from APIs or browser events.
Top-Level await
Modern JavaScript modules can use await at the top level.
Example module:
const response =
await fetch("/api/products");
const products =
await response.json();
This requires module context.
For example:
<script
type="module"
src="app.js"
></script>
Top-level await can affect module loading, so beginners should not use it everywhere.
For this course, prefer clear async functions until module behavior is familiar.
async/await and Modules
Example:
export async function loadUser() {
const response =
await fetch("/api/user");
return response.json();
}
Another module can import and await it.
You will study modules in a separate lesson.
Common Beginner Mistakes
Using await Outside an Async Function
Wrong in a normal function:
function loadData() {
const data =
await getData();
}
Correct:
async function loadData() {
const data =
await getData();
}
Forgetting That an async Function Returns a Promise
This:
async function getNumber() {
return 10;
}
const number = getNumber();
does not put 10 directly into number.
It gives you a Promise.
Use:
const number =
await getNumber();
inside another async function.
Forgetting await
Wrong when you need the resolved value:
async function run() {
const user =
getUser();
console.log(user.name);
}
user is a Promise.
Use:
const user =
await getUser();
Using await on Every Value
Do not write await around normal synchronous values without need.
It makes the code look asynchronous when it is not.
Forgetting try…catch
Awaited Promises can reject.
Real applications need a plan for failure.
Assuming fetch() Rejects on Every HTTP Error
It normally does not reject only because of a 404 or 500.
Check:
response.ok
and throw an error when needed.
Running Independent Requests One After Another
Avoid:
const user =
await getUser();
const settings =
await getSettings();
when the requests do not depend on each other.
Use Promise.all() when both results are required and can begin together.
Using Promise.all() for Dependent Requests
Do not start:
getOrders(user.id)
before you have the user ID.
Use sequential await when later work depends on earlier results.
Using forEach() With async and Expecting It to Wait
This is a common mistake:
items.forEach(async (item) => {
await saveItem(item);
});
Use for...of for sequential work or Promise.all() with map() for parallel work.
Forgetting return in an async Function
If callers need a result, return it:
async function getUser() {
const user =
await loadUser();
return user;
}
Catching Errors and Hiding Them Accidentally
This:
async function loadData() {
try {
return await requestData();
} catch (error) {
console.log(error);
}
}
turns the failure into a fulfilled result of undefined unless another error is thrown.
That may be fine for some optional tasks, but it may hide a failure from callers.
Using One Huge async Function
Asynchronous code can become difficult to maintain when one function loads data, validates it, updates several page areas, and handles every error.
Break larger workflows into focused functions.
Disabling a Button and Never Re-Enabling It
If you disable a submit button before an async request, use finally or another reliable path to restore it after failure when appropriate.
Assuming await Freezes the Browser
await pauses that async function’s progress.
It does not block the whole browser while waiting for a Promise.
Best Practices for JavaScript Async/Await
Understand Promises before relying heavily on async/await.
Use async only when a function performs or coordinates asynchronous work.
Use await when you need a Promise’s settled value before continuing.
Use try...catch around workflows that can fail.
Use finally for cleanup such as hiding loading states or restoring buttons.
Check HTTP response status when using Fetch.
Use sequential await when tasks depend on one another.
Use Promise.all() when independent tasks can run together and all are required.
Use Promise.allSettled() when every outcome matters independently.
Avoid forEach(async ...) when you need to wait for the operations.
Keep async functions focused.
Return useful values so callers can compose your functions.
Do not hide important errors with vague fallback values.
Show loading, success, empty, and error states in real user interfaces.
Beginner Exercise
Create:
function getMessage() {
return new Promise((resolve) => {
setTimeout(() => {
resolve(
"Async/Await is working"
);
}, 1000);
});
}
Now create:
async function showMessage() {
// your code
}
Use:
await
to get the message.
Print it in the Console.
The result after about one second should be:
Async/Await is working
Challenge Exercise
Create:
function checkStock(stock) {
return new Promise(
(resolve, reject) => {
setTimeout(() => {
if (stock > 0) {
resolve(
"Product available"
);
} else {
reject(
new Error(
"Product out of stock"
)
);
}
}, 500);
}
);
}
Create an async function that:
- Uses
await checkStock(). - Handles success.
- Handles failure with
try...catch. - Prints
Stock check completeinfinally.
Test with:
5
and:
0
Extra Challenge
Create:
function loadUser() {
return Promise.resolve({
name: "Riya"
});
}
function loadProducts() {
return Promise.resolve([
"Laptop",
"Phone"
]);
}
function loadCategories() {
return Promise.resolve([
"Computers",
"Mobiles"
]);
}
Use:
Promise.all()
inside an async function to load all three at the same time.
Destructure the results and print them.
Then make one function reject and handle the error with try...catch.
Frequently Asked Questions
What is async/await in JavaScript?
async/await is syntax built on Promises that makes many asynchronous workflows easier to read and write.
What does async do in JavaScript?
Adding async to a function makes that function return a Promise.
A returned normal value becomes the fulfillment value of that Promise.
What does await do in JavaScript?
await waits inside an async function for a Promise to settle and gives you its fulfilled value.
If the Promise rejects, the await expression throws that rejection reason.
Does an async function always return a Promise?
Yes.
Even when you return a normal value, JavaScript wraps it in a fulfilled Promise.
Can I use await outside an async function?
Normally, use await inside an async function.
Top-level await is also supported in modern JavaScript modules, but that is a separate module feature.
Does await block JavaScript?
It pauses the current async function until the awaited result is ready.
It does not block the whole browser or stop all JavaScript work.
How do I handle async/await errors?
Use:
try {
const data =
await loadData();
} catch (error) {
console.error(error);
}
Can I use finally with async/await?
Yes.
finally runs after the try or catch path finishes.
It is useful for cleanup.
What happens when an async function throws an error?
The Promise returned by that async function becomes rejected.
What is the difference between Promises and async/await?
Promises are the underlying asynchronous result objects.
async/await is syntax for working with Promises.
Is async/await better than then()?
Neither is always better.
async/await is often clearer for sequential workflows.
Promise methods remain useful for composition such as Promise.all() and some direct chains.
Can I use Promise.all() with async/await?
Yes.
Example:
const [user, products] =
await Promise.all([
getUser(),
getProducts()
]);
Why use Promise.all() instead of several awaits?
When tasks are independent, Promise.all() lets them begin together rather than waiting for each one sequentially.
Should I use Promise.all() when requests depend on each other?
No.
If one request needs the result of another, use sequential await.
Can I use await inside a loop?
Yes.
for (const item of items) {
await processItem(item);
}
This runs the operations sequentially.
Why does forEach() not wait for async callbacks?
forEach() does not combine or await the Promises returned by its callback.
Use for...of for sequential work or Promise.all() with map() for parallel work.
Can event listeners be async?
Yes.
Example:
button.addEventListener(
"click",
async () => {
const data =
await loadData();
}
);
Handle expected errors inside the async handler.
Can I use await with setTimeout()?
Not directly, because setTimeout() does not return a Promise.
Wrap it in a Promise first.
Does fetch() work with async/await?
Yes.
A common pattern is:
const response =
await fetch(url);
const data =
await response.json();
Does fetch() throw on a 404 response?
Not normally.
Check:
response.ok
and throw an error yourself when the HTTP status is not acceptable.
What should I learn after async/await?
Learn the JavaScript Fetch API next. It is one of the most common real-world places where Promises and async/await are used together.
Summary
JavaScript async and await make Promise-based code easier to read.
An async function:
async function getData() {
return "Data";
}
always returns a Promise.
Inside an async function, await gives you the fulfilled Promise value:
const data =
await getData();
Use:
try
catch
finally
to handle failure and cleanup.
You also learned how to:
- Create async functions
- Use async arrow functions
- Await Promises
- Understand async return values
- Handle rejected Promises
- Throw errors
- Use
try...catch - Use
finally - Run dependent tasks sequentially
- Run independent tasks with
Promise.all() - Use
Promise.allSettled() - Use async event handlers
- Await work inside loops
- Avoid
forEach(async ...)mistakes - Work with Fetch-style Promises
- Handle loading and error states
- Avoid unnecessary sequential waits
Async/await is one of the most important modern JavaScript patterns because real frontend applications frequently communicate with APIs and other asynchronous browser features.
Continue Learning JavaScript
Previous Lesson: JavaScript Promises Explained
Course Home: JavaScript Tutorial for Beginners
Next Lesson: JavaScript Fetch API Explained
In the next lesson, you will use fetch(), Promises, async/await, JSON, HTTP methods, headers, request bodies, response status checks, loading states, and error handling to work with real APIs.
