Q1If delay(100, "A").then((v) => console.log(v)); is followed by console.log("B");, in what order is the output printed?
Promise — Receiving Async Results with then and catch
Receive the result of a slow operation later with Promise: its three states, then / catch / finally, chaining, and Promise.all.
Some operations, like submitting an order or checking stock, take a while to return a result. To keep the page from freezing in the meantime, you write this kind of code so that it moves on to the next line right away and picks up the result later. This style is called asynchronous programming.
This article covers Promise, which represents a result that's settled later, and Promise.all, which waits for several Promises at once.
Receiving a Result Later — Promise and then
Say it takes 100ms to hear back about seat availability. setTimeout (a function that calls a function once, after the given number of milliseconds) returns immediately, and execution moves on to the next line, so you can't return an answer that arrives 100ms later from your function.
Instead, you return a Promise (an object representing a result that's settled later), and the answer is delivered through it. The function you pass to new Promise((resolve) => { ... }) is called immediately, and calling resolve(value) inside it moves the Promise from pending (waiting for a result) to fulfilled (succeeded). The function you pass to the then method is called with that value once the Promise is fulfilled.
// Returns a Promise that's fulfilled with value after ms milliseconds
function delay(ms, value) {
return new Promise((resolve) => {
setTimeout(() => resolve(value), ms);
});
}
// Start the check (still pending at this point)
const seats = delay(100, "12 seats left");
// Pass the function to call once it's fulfilled
seats.then((message) => {
console.log(message); // 12 seats left (printed 2nd)
});
console.log("Checking seat availability"); // Checking seat availability (printed 1st)
then just holds on to the function; it doesn't call it on the spot. The function you pass runs only after the lines below have finished and the Promise is fulfilled.If you write code that uses the result outside then, it runs before the result is settled, so the value isn't available yet. Keep the lines that use the result inside the function you pass to then, and put anything you want to do while waiting on the lines after it, outside then.
Add a Line at the End That Waits for then
The code runner on this page only collects output printed before the last line finishes. In a browser or Node.js, the then function runs even if nothing waits for it, but here you add await delay(1000); at the end to wait one second. await (syntax that doesn't move on to the next line until a Promise's result is settled) is covered in the next article.
Handling Failure — reject / catch / finally
Verifying a card PIN fails when the number is wrong. If all you have is resolve, a failure arrives as an ordinary value just like a success, so the receiving function needs an if every time to tell them apart.
The function passed to new Promise can also receive reject as its second parameter. Calling it, as in reject(new Error("message")), makes the Promise rejected (failed). The function passed to catch receives that Error when the Promise is rejected, and the function passed to finally is called last, whether it succeeded or failed.
function verifyPin(pin) {
return new Promise((resolve, reject) => {
setTimeout(() => {
if (pin === "4821") {
resolve("PIN verified");
} else {
// Signal a failure by passing an Error to reject
reject(new Error("Incorrect PIN"));
}
}, 100);
});
}
// then, catch, and finally can be chained one after another (the next section explains how)
verifyPin("0000")
.then((message) => console.log(message)) // not called, since it's rejected
.catch((error) => console.log(error.message)) // Incorrect PIN
.finally(() => console.log("Cleared the input field"));
// Cleared the input field (prints last, on success or failure)
When a Promise is rejected, the then functions chained before catch are skipped, and the catch function after them is called. This catch is a Promise method, separate from the try / catch statement. If you never call either resolve or reject, the code appears to hang with no output and no error.
Running Steps in Order — Chaining then and return
Say that after placing an order, you want to send a confirmation email using the order number that comes back. If you write the second request inside the first then, and the third inside that one, the indentation gets deeper with every step and the code gets hard to read.
then returns a new Promise, so you can link calls as in .then(...).then(...). This is called a chain (attaching the next then to the Promise the previous then returns). A value you return from a then function is received by the next then function.
// delay is the same as in the first section's code
const placeOrder = () => delay(100, "A-2046");
const sendMail = (orderId) => delay(100, `Sent a confirmation email for ${orderId}`);
placeOrder()
.then((orderId) => {
console.log(`Order number: ${orderId}`); // Order number: A-2046
return sendMail(orderId); // the chain waits until the email is sent
})
.then((result) => {
console.log(result); // Sent a confirmation email for A-2046
return "All steps complete"; // the value goes straight to the next function
})
.then((status) => console.log(status)); // All steps complete
Without return, the function returns undefined, so the next then runs without waiting for the email to be sent, and result is undefined. No error appears; the send result is simply lost, which makes this mistake easy to miss.
Once You Add Braces, You Can't Leave Out return
An arrow function without braces, like .then((id) => sendMail(id)), returns the Promise from sendMail as-is, so the next then waits. If you wrap the body in braces to add a log line partway through, add return back, as in return sendMail(id);.
Waiting for Several Results Together — Promise.all
Say that when the admin dashboard opens, you request today's sales, order count, and inquiry count separately, and show the summary panel once all three are in. If you attach a separate then to each of the three Promises, the one that arrives last differs every time, so there's no single place to check that they've all arrived.
Call the three functions and pass the resulting Promises to Promise.all (a method that takes an array of Promises and waits until all their results are in), and you receive all three results at once as an array. If even one is rejected, it goes to catch instead of then.
// delay is the same as in the first section's code
const loadSales = () => delay(100, 184000);
const loadOrders = () => delay(50, 42);
const loadInquiries = () => delay(80, 3);
// All three start as soon as the functions are called. Promise.all waits until they're all in
Promise.all([loadSales(), loadOrders(), loadInquiries()])
// Receive the result array by destructuring it by position
.then(([sales, orders, inquiries]) => {
// Received in the order written in the array, not the order they finished
console.log(`Sales: ${sales} yen`); // Sales: 184000 yen
console.log(`Orders: ${orders}`); // Orders: 42
console.log(`Inquiries: ${inquiries}`); // Inquiries: 3
})
.catch((error) => console.log(error.message));
Promise.all itself doesn't start any requests; it only waits until they're all in. The table below shows, for each state of the Promises passed in, the Promise that Promise.all returns and which function gets called.
| State of the Promises passed in | Promise returned by Promise.all | Function called and value received |
|---|---|---|
| All fulfilled | Fulfilled once the last one is fulfilled | then receives an array in the order passed in |
| One is rejected | Rejected as soon as that one is rejected | catch receives that Error |
| Two or more are rejected | Rejected as soon as the first one is rejected | catch receives only the first Error |
| The rest are fulfilled, but one stays pending | Stays pending | Neither then nor catch is called |
Knowledge Check
Answer each question one by one.
Q2If you chain .then(A).catch(B).finally(C) onto a Promise that gets rejected, which ones are called?
Q3What array does the then of Promise.all([delay(100, "A"), delay(50, "B")]) receive?