Asynchronous processing
A mechanism for progressing time-consuming processing (communication, timers, and so on) without stopping other processing.
What is asynchronous processing
Asynchronous processing works with the flow of "once you start the processing, move on to the next without waiting for the result, and receive a notification when it completes."
Callback functions
The most basic method, where you pass a function to be called after the processing completes.
function fetchData(callback) {
setTimeout(() => {
const data = { name: "Data", value: 42 };
callback(data);
}, 1000);
}
fetchData((data) => {
console.log("Data was fetched:", data);
});
console.log("Called fetchData"); // This line runs first
Nesting callbacks many levels deep results in hard-to-read code that gets deeper and deeper to the right (callback hell). Promises and async/await were introduced to solve this.
step1((result1) => {
step2(result1, (result2) => {
step3(result2, (result3) => {
console.log("Final result:", result3); // It gets deep and hard to read
});
});
});
Promise
A Promise is an object that represents "a promise that a result will eventually be returned." It transitions through the following states.
const myPromise = new Promise((resolve, reject) => {
const success = true;
if (success) {
resolve("The processing succeeded!");
} else {
reject(new Error("An error occurred"));
}
});
myPromise
.then((result) => console.log(result)) // On success
.catch((error) => console.error(error)) // On failure
.finally(() => console.log("Processing complete")); // On either success or failure
Handling multiple Promises together
const p1 = Promise.resolve("Success 1");
const p2 = Promise.reject("Failure");
const p3 = Promise.resolve("Success 2");
// Promise.all: returns an array if all succeed. If even one fails, the whole thing fails
Promise.all([p1, p3]).then((results) => {
console.log(results); // ['Success 1', 'Success 2']
});
// Promise.allSettled: waits for all of them. It doesn't stop even if there's a failure (ES2020)
Promise.allSettled([p1, p2, p3]).then((results) => {
console.log(results);
// [{ status: 'fulfilled', value: 'Success 1' }, { status: 'rejected', reason: 'Failure' }, ...]
});
// Promise.any: returns the first one that succeeds (ES2021)
Promise.any([p2, p1]).then((result) => {
console.log(result); // Success 1
});
- You want them all to succeed →
Promise.all - You want all the results regardless of success or failure →
Promise.allSettled - It's enough if any one succeeds →
Promise.any
Async/Await (ES2017)
Using async / await, you can write asynchronous processing in a straight line, like synchronous processing. Internally it uses Promises.
async function fetchUserData() {
try {
// Wait for the Promise's result with await
const response = await fetch("https://api.example.com/user");
const userData = await response.json();
console.log("User data:", userData);
return userData;
} catch (error) {
console.error("Failed to fetch data:", error);
throw error;
}
}
Serial and parallel
// Serial: move to the next only after the previous await finishes (longer total time)
const a = await fetchA();
const b = await fetchB();
// Parallel: run them at the same time and wait for both (fast)
const [x, y] = await Promise.all([fetchA(), fetchB()]);
The processing you register with Promise.then() goes into a queue called the microtask queue, and it runs before normal tasks such as setTimeout. If you remember that things run in the order "synchronous code → the Promise's then → setTimeout," it becomes easier to predict the execution order of asynchronous code.
What to read next
- Best practices — How to write good code based on the knowledge so far