Skip to main content

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
Callback hell

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
});
Choosing between them
  • 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()]);
Execution order and microtasks

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.

  • Best practices — How to write good code based on the knowledge so far