Back to JavaScript
2026-04-077 min read

async functions (JavaScript)

Learn async functions (JavaScript) step by step with clear examples and exercises.

Why This Matters

Async functions are an essential part of modern JavaScript development, simplifying asynchronous programming and making it easier to write cleaner, more manageable code. They help prevent blocking the main thread, ensuring smooth and responsive user interfaces. Understanding async functions is crucial for writing efficient JavaScript code in real-world applications like web development, server-side programming, and more.

Async functions allow developers to write asynchronous code using a syntax that closely resembles synchronous code, making it easier to reason about the flow of execution and reducing the likelihood of errors. This guide will walk you through the core concept of async functions, provide a worked example, discuss common mistakes, offer practice questions, and answer frequently asked questions.

Prerequisites

Before diving into async functions, you should have a solid understanding of the following:

  1. Basic JavaScript syntax and variables
  2. Callback functions
  3. Promises
  4. ES6 arrow functions
  5. Understanding the concept of asynchronous programming
  6. Familiarity with the fetch API for making HTTP requests
  7. Understanding how to handle errors using try-catch blocks
  8. Knowledge of file system operations (e.g., reading files) using Node.js's built-in fs module
  9. Comfort working with higher-order functions like Promise.all() and Promise.race()
  10. Familiarity with transpilers like Babel for browser compatibility

Core Concept

Async functions are a type of function declared using the async keyword, which returns a Promise by default. They allow you to write asynchronous code in a more organized and readable manner without having to explicitly handle Promises. The await keyword is used within async functions to pause the execution of the function until a Promise is resolved or rejected.

async function fetchData() {
try {
const response = await fetch('https://api.example.com/data');
const data = await response.json();
console.log(data);
} catch (error) {
console.error('Error fetching data:', error);
}
}

In the example above, fetchData is an async function that fetches data from an API and logs it to the console. The await keyword is used twice: once for the fetch request to resolve, and again for the response to be converted to JSON.

Async functions can also be used with traditional callbacks by calling them with the .then() method on their returned Promise. This allows you to use async functions as a bridge between older callback-based code and modern async/await syntax.

function fetchDataCallback(callback) {
const xhr = new XMLHttpRequest();
xhr.open('GET', 'https://api.example.com/data');
xhr.onreadystatechange = function() {
if (xhr.readyState === 4 && xhr.status === 200) {
callback(JSON.parse(xhr.responseText));
}
};
xhr.send();
}

async function fetchDataAndLog(callback) {
const data = await new Promise((resolve, reject) => {
fetchDataCallback(data => resolve(data));
});
console.log(data);
}

In this example, fetchDataCallback is a traditional callback function that fetches data using the XMLHttpRequest API. fetchDataAndLog is an async function that uses new Promise() to wrap the callback-based fetch and log the result using the async/await syntax.

Worked Example

Let's create a simple example of an async function that fetches data from an API, processes it, and logs the result.

async function processData() {
try {
const response = await fetch('https://api.example.com/data');
const data = await response.json();

// Process the data here (e.g., filter, sort, etc.)
const filteredData = data.filter(item => item.id % 2 === 0);

console.log('Filtered Data:', filteredData);
} catch (error) {
console.error('Error fetching data:', error);
}
}

In this example, the processData function fetches data from an API, filters it to include only even-numbered items, and logs the filtered data. If there's an error during the process, it will be caught and logged instead.

Common Mistakes

  1. Forgetting the async keyword: Without the async keyword, a function is not considered an async function and cannot use the await keyword.
  2. Using await outside an async function: The await keyword can only be used within an async function.
  3. Not handling errors properly: It's essential to handle errors using try-catch blocks when working with async functions, as they return Promises that may be rejected.
  4. Ignoring the returned Promise: Always call an async function using await or .then() to ensure you handle the returned Promise correctly.
  5. Misusing await in a loop: Using await inside a loop can lead to unexpected behavior, as it may pause the entire loop instead of just the individual iteration. Instead, use for-await...of loops or Promise.all().
  6. Nesting too many async functions: While async functions make asynchronous code easier to write and read, nesting too many can lead to complex, hard-to-follow code. Consider using higher-order functions like Promise.all() or Promise.race() to manage multiple Promises more effectively.
  7. Not properly handling CORS issues: If the API you're fetching data from has Cross-Origin Resource Sharing (CORS) restrictions, you may encounter errors. To work around this, consider using a proxy server or setting up CORS on your API.
  8. Using await with non-Promise values: The await keyword can only be used with Promises. If you try to use it with a non-Promise value, the function will immediately return that value instead of waiting for a Promise to resolve.
  9. Not returning a value from an async function: If an async function doesn't explicitly return a value, it will implicitly return undefined. To work around this, you can use return statements or make sure your async function is called with the await keyword.
  10. Not considering edge cases: When working with asynchronous code, it's essential to consider edge cases like network errors, timeouts, and race conditions that may affect the behavior of your code.

Subheadings under Common Mistakes:

  • Error Handling
  • Returning Non-Promises
  • Nesting and Managing Multiple Promises
  • CORS Issues
  • Edge Cases

Practice Questions

  1. Write an async function that fetches data from two different APIs and logs the combined results.
async function fetchCombinedData() {
try {
const api1Response = await fetch('https://api1.example.com/data');
const api2Response = await fetch('https://api2.example.com/data');
const data1 = await api1Response.json();
const data2 = await api2Response.json();
console.log(data1.concat(data2));
} catch (error) {
console.error('Error fetching data:', error);
}
}
  1. Create an async function that reads a file line by line and logs each line to the console.
async function readFileLineByLine(filePath) {
const file = await fs.promises.readFile(filePath, 'utf8');
const lines = file.split('\n');
for (const line of lines) {
console.log(line);
}
}
  1. Given the following code snippet, what will be logged to the console? Why?
async function example() {
const response = await fetch('https://api.example.com/data');
const data = await response.json();
console.log(data);
}

example().then(() => console.log('This will log after the async function finishes.'));

In this example, the example() function returns a Promise that resolves when the async function finishes executing. The .then() method is used to handle the returned Promise and log a message to the console after the async function has completed.

FAQ

  1. Why use async functions instead of Promises? Async functions provide a more readable and easier-to-manage syntax for asynchronous code, as they automatically return Promises and allow the use of await.
  2. Can I mix async and non-async functions in the same file? Yes, you can call an async function from a non-async function or vice versa. However, be mindful of how you handle the returned Promises.
  3. What happens if multiple await statements are used without proper handling of the returned Promises? If multiple await statements are used without proper handling, the code may throw an error, as each await statement returns a Promise that needs to be handled with either .then(), .catch(), or await.
  4. Can I use async functions in older browsers? Async functions were introduced in ES2017 (Chrome 59, Firefox 56, Edge 16, and Safari 11), so they may not be supported in older browsers. You can use transpilers like Babel to convert async functions to Promises or callbacks for better browser compatibility.
  5. How do I handle CORS issues when using async functions with the fetch API? To work around CORS issues, consider using a proxy server or setting up CORS on your API. If you're using a proxy server, make sure to adjust the fetch URL accordingly.
  6. What are some best practices for writing clean and maintainable async code? Some best practices include:
  • Using descriptive variable names
  • Keeping functions short and focused
  • Avoiding deep nesting of async functions
  • Documenting your code with comments and JSDoc-style comments
  • Testing your code thoroughly to ensure it works as expected in various scenarios
  1. How can I improve the performance of my async functions? To improve the performance of your async functions, consider:
  • Minimizing the number of network requests by fetching data in bulk or using caching strategies
  • Using a CDN to serve static assets closer to the user
  • Optimizing server-side code to reduce response times
  • Implementing lazy loading for heavy resources like images or videos
  1. What are some common pitfalls to avoid when working with async functions? Some common pitfalls include:
  • Forgetting to handle errors properly
  • Misusing await in a loop or with non-Promises
  • Not returning a value from an async function
  • Ignoring the returned Promise when calling an async function
  • Not considering edge cases like network errors, timeouts, and race conditions
async functions (JavaScript) | JavaScript | XQA Learn