Synchronous vs Asynchronous Code
AI Using GitHub Copilot in VS Code
In the previous chapters you learned how to:
.then() / .catch().fs.promises.On this page we will:
async / await as a more readable way to work with promises.async / await.async / await fits in the event loop model you just learned.Promise.all() and use it together with fetch() to run multiple requests in parallel.<aside> ⚠️
async / await does not replace promises. It is just a more convenient way to use them. Under the hood, everything is still promises and microtasks.
</aside>
Let us start from a pattern you already know from the previous chapters:
function whatIsTheMeaningOfLife() {
return new Promise((resolve, reject) => {
setTimeout(() => {
if (Math.random() > 0.5) {
resolve(42);
} else {
reject(new Error('Come back in 7.5 million years and ask me again!'));
}
}, 1000);
});
}
function ask() {
let text = new Date().toISOString() + ' ';
return whatIsTheMeaningOfLife()
.then((result) => {
text += `The answer is ${result}`;
})
.catch((err) => {
text += `Error: ${err.message}`;
})
.then(() => {
console.log(text);
});
}
ask();
This code:
.then() to handle the success case..catch() to handle errors.We can write the same logic using async / await as follows:
function whatIsTheMeaningOfLife() {
return new Promise((resolve, reject) => {
setTimeout(() => {
if (Math.random() > 0.5) {
resolve(42);
} else {
reject(new Error('Come back in 7.5 million years and ask me again!'));
}
}, 1000);
});
}
async function ask() {
let text = new Date().toISOString() + ' ';
try {
const result = await whatIsTheMeaningOfLife();
text += `The answer is ${result}`;
} catch (err) {
text += `Error: ${err.message}`;
}
console.log(text);
}
ask();
What changed?
async keyword in front of the ask function.ask, we used await in front of whatIsTheMeaningOfLife()..then() / .catch() with try { ... } catch (err) { ... }.Conceptually:
await somePromise pauses the async function until the promise settles.await evaluates to the fulfillment value.await throws that error, which you handle with try / catch.<aside> ⚠️
You can only use await inside a function marked with async (or at the top level in modern Node with ESM modules). Using await in a normal function results in a syntax error.
</aside>
Even though they look like ordinary functions, async functions always return a promise.
async function add(a, b) {
return a + b;
}
const resultPromise = add(2, 3);
console.log(resultPromise); // Promise { 5 }
resultPromise.then((value) => {
console.log('Value:', value); // Value: 5
});
Rules to remember:
async function always returns a promise.return a value, the promise is fulfilled with that value.throw an error, the promise is rejected with that error.This connects directly to the mental model from the Promises chapter: async / await is just “syntax sugar” around Promise objects and .then() / .catch().
In the Fetch API chapter you wrote code like this:
const url = '<https://jsonplaceholder.typicode.com/posts/1>';
fetch(url)
.then((response) => {
if (!response.ok) {
throw new Error(`Request failed with status ${response.status}`);
}
return response.json();
})
.then((data) => {
console.log('Data:', data);
})
.catch((error) => {
console.error('Error while fetching:', error.message);
});
Let us now rewrite this as an async function:
const url = '<https://jsonplaceholder.typicode.com/posts/1>';
async function fetchPost() {
try {
const response = await fetch(url);
if (!response.ok) {
throw new Error(`Request failed with status ${response.status}`);
}
const data = await response.json();
console.log('Data:', data);
} catch (error) {
console.error('Error while fetching:', error.message);
}
}
fetchPost();
Compare the two versions:
.then().await lines read like “normal” sequential code..catch() into a try / catch block.Under the hood, both versions behave the same with respect to:
Because await can be used almost anywhere inside an async function, we can mix synchronous logic and asynchronous calls in a way that often feels more natural.
Consider this example that first checks some input synchronously and then fetches data:
async function fetchUserPost(userId, postId) {
if (typeof userId !== 'number' || typeof postId !== 'number') {
throw new Error('userId and postId must be numbers');
}
const url = `https://jsonplaceholder.typicode.com/posts/${postId}`;
const response = await fetch(url);
if (!response.ok) {
throw new Error(`Request failed with status ${response.status}`);
}
const post = await response.json();
return post;
}
fetchUserPost(1, 1)
.then((post) => {
console.log('Post:', post.title);
})
.catch((error) => {
console.error('Error:', error.message);
});
Here you see all the concepts you have already practiced, just with different syntax:
response.ok to treat non‑2xx statuses as errors.await to pause until each promise settles.So far we have mostly done one asynchronous operation at a time.
But in many real‑world scenarios you want to:
That is where Promise.all() comes in.
Promise.all() takes an array of promises and returns a single promise that:
const p1 = Promise.resolve(1);
const p2 = Promise.resolve(2);
const p3 = Promise.resolve(3);
Promise.all([p1, p2, p3]).then((values) => {
console.log(values); // [1, 2, 3]
});
If any of the promises fails:
const ok = Promise.resolve('ok');
const fail = Promise.reject(new Error('Boom'));
Promise.all([ok, fail])
.then((values) => {
// This will not run
})
.catch((err) => {
console.error('At least one promise failed:', err.message);
});
fetch() returns a promise. That means you can pass multiple fetch() calls into Promise.all() to:
This is usually much faster than waiting for each request one by one.
First, here is a promise‑chain version using the Fetch API and Promise.all():
const urls = [
'<https://jsonplaceholder.typicode.com/posts/1>',
'<https://jsonplaceholder.typicode.com/posts/2>',
'<https://jsonplaceholder.typicode.com/posts/3>',
];
Promise.all(urls.map((url) => fetch(url)))
.then((responses) => {
responses.forEach((response) => {
if (!response.ok) {
throw new Error(`Request failed with status ${response.status}`);
}
});
// Turn each Response into a promise for its JSON body
return Promise.all(responses.map((response) => response.json()));
})
.then((posts) => {
console.log('Received posts:');
posts.forEach((post) => {
console.log('-', post.title);
});
})
.catch((error) => {
console.error('Error while fetching posts:', error.message);
});
Now the exact same idea using async / await:
const urls = [
'<https://jsonplaceholder.typicode.com/posts/1>',
'<https://jsonplaceholder.typicode.com/posts/2>',
'<https://jsonplaceholder.typicode.com/posts/3>',
];
async function fetchMultiplePosts() {
try {
// Start all fetches in parallel
const responses = await Promise.all(urls.map((url) => fetch(url)));
// Check statuses
for (const response of responses) {
if (!response.ok) {
throw new Error(`Request failed with status ${response.status}`);
}
}
// Read all bodies in parallel
const posts = await Promise.all(
responses.map((response) => response.json())
);
console.log('Received posts:');
for (const post of posts) {
console.log('-', post.title);
}
} catch (error) {
console.error('Error while fetching posts:', error.message);
}
}
fetchMultiplePosts();
Key ideas:
[urls.map](<http://urls.map>)((url) => fetch(url)) returns an array of promises.Promise.all([...]) waits until all those promises are fulfilled.Promise.all to read all the JSON bodies in parallel.try / catch handles any error from any of the requests.<aside> 💡
</aside>
We have include an example in the Learning-Resources GitHub repository that visualises the effect of Promise.all(). You can find that example here:
It is a simple web application that show three cats walking across the screen from left to right, while pausing in the middle to do a little dance. Here is what it looks like when you press the Start button:

Here is an extract of the code for this example which we will break down below.