ES2017 introduced async/await, which lets you write asynchronous code that looks and reads almost like synchronous code. Under the hood it’s still Promises — async/await is syntactic sugar — but the result is far easier to follow and debug than long .then() chains.
Problem: How do you write asynchronous JavaScript that fetches data from an API without deeply nested .then() callbacks?
Solution: Use async/await — declare a function as async and use the await keyword before any Promise-returning call. Wrap in try/catch to handle network errors and non-2xx responses cleanly.
A basic example — fetching JSON from an API:
async function fetchUser( id ) {
try {
const response = await fetch( `https://jsonplaceholder.typicode.com/users/${id}` );
if ( ! response.ok ) {
throw new Error( `HTTP error: ${response.status}` );
}
const user = await response.json();
return user;
} catch ( error ) {
console.error( 'Failed to fetch user:', error );
return null;
}
}
// Call it
fetchUser( 1 ).then( user => console.log( user.name ) );
Running multiple requests in parallel with Promise.all():
async function fetchMultiple( ids ) {
const promises = ids.map( id =>
fetch( `https://jsonplaceholder.typicode.com/posts/${id}` ).then( r => r.json() )
);
const posts = await Promise.all( promises );
return posts;
}
fetchMultiple( [1, 2, 3] ).then( posts => console.log( posts ) );
Sequential execution when each request depends on the previous result:
async function getUserPosts( userId ) {
const user = await fetchUser( userId );
if ( ! user ) return [];
const res = await fetch( `https://jsonplaceholder.typicode.com/posts?userId=${user.id}` );
const posts = await res.json();
return posts;
}
NOTE: An async function always returns a Promise. Calling await outside an async function is a syntax error. Always wrap await calls in try/catch — unhandled Promise rejections can silently fail in older environments.