Web Kokua Banner Image


Promises

A JavaScript promise is an object that represents the eventual completion or failure of an asynchronous operation. It is a way to handle asynchronous code in a more structured and predictable way.

Promises are created using the Promise constructor. The constructor takes a function as an argument, which is called the executor function. The executor function takes two arguments: a resolve function and a reject function. The resolve function is called when the asynchronous operation completes successfully, and the reject function is called when the asynchronous operation fails.

Once a promise is created, it can be resolved or rejected using the resolve and reject methods. The resolve method takes a value as an argument, and the reject method takes an error as an argument.

Promises can be chained together using the then method. The then method takes two functions as arguments: a success function and a failure function. The success function is called when the promise is resolved, and the failure function is called when the promise is rejected.

The following code shows how to create a promise and chain it together with another promise:


const promise1 = new Promise((resolve, reject) => { // DO SOMETHING ASYNCHRONOUS resolve("Success!"); }); const promise2 = promise1.then((value) => { // DO SOMETHING WITH THE VALUE console.log(value); });

In this code, the promise1 promise is created and resolved with the value "Success!". The promise2 promise is then chained to the promise1 promise. The promise2 promise will be resolved with the value "Success!" when the promise1 promise is resolved.

Promises are a powerful tool for handling asynchronous code in JavaScript. They make it easier to write code that is more predictable and easier to read.

The Promise object represents the eventual completion (or failure) of an asynchronous operation and its resulting value. A Promise is a proxy for a value not necessarily known when the promise is created. It allows you to associate handlers with an asynchronous action's eventual success value or failure reason. This lets asynchronous methods return values like synchronous methods: instead of immediately returning the final value, the asynchronous method returns a promise to supply the value at some point in the future.


Promise States

A Promise is in one of these states:

The eventual state of a pending promise can either be fulfilled with a value or rejected with a reason (error). When either of these options occur, the associated handlers queued up by a promise's then method are called. If the promise has already been fulfilled or rejected when a corresponding handler is attached, the handler will be called, so there is no race condition between an asynchronous operation completing and its handlers being attached.

A promise is said to be settled if it is either fulfilled or rejected, but not pending.

Essentially, a promise is a returned object to which you attach callbacks, instead of passing callbacks into a function. Imagine a function, createAudioFileAsync(), which asynchronously generates a sound file given a configuration record and two callback functions: one called if the audio file is successfully created, and the other called if an error occurs.


Constructor Syntax

new Promise(executor);

Parameters

executor - A function to be executed by the constructor.
It receives two functions as parameters: resolveFunction and rejectFunction. The executor is custom code that ties an outcome in a callback to a promise. The executor's signature is expected to be something like:

function executor(resolveFunction, rejectFunction) { // Typically, some asynchronous operation that accepts a callback. }

Any errors thrown in the executor will cause the promise to be rejected, and the return value will be neglected.

Return Value

When called via new, the Promise constructor returns a promise object.
The promise object will become resolved when either of the functions resolveFunc or rejectFunc are invoked.
Note that if you call resolveFunc or rejectFunc and pass another Promise object as an argument, it can be said to be "resolved", but still not "settled".


createAudioFileAsync(audioSettings, successCallback, failureCallback); function successCallback(result) { console.log('Audio file ready at URL: ${result}'); } function failureCallback(error) { console.error('Error generating audio file: ${error}'); }

If createAudioFileAsync() were rewritten to return a promise, you would attach your callbacks to it instead:

createAudioFileAsync(audioSettings).then(successCallback, failureCallback); function successCallback(result) { console.log('Audio file ready at URL: ${result}'); } function failureCallback(error) { console.error('Error generating audio file: ${error}'); }

When & How To Use Promises

JavaScript is single threaded, meaning that two bits of script cannot run at the same time; they have to run one after another sequentially. In browsers, JavaScript shares a thread with a load of other stuff that differs from browser to browser. But typically JavaScript is in the same queue as painting, updating styles, and handling user actions (such as highlighting text and interacting with form controls). Activity in one of these things delays the others.

var img1 = document.querySelector(".img-1"); img1.addEventListener('load', function(){ // IMAGE HAS BEEN LOADED }); img1.addEventListener('error', function(){ // AN ERROR HAS OCCURED, IMAGE HAS NOT BEEN LOADED });

A JavaScript Promise object contains both the producing code and calls to the consuming code:

let myPromise = new Promise(function(myResolve, myReject) { // "PRODUCING CODE" (MAY TAKE SOME TIME) myResolve(); // when successful myReject(); // when error }); // "CONSUMING CODE" (MUST WAIT FOR A FULFILLED PROMISE) myPromise.then( function(value) { /* code if successful */ }, function(error) { /* code if some error */ } );
myPromise.then( function(value) { /* code if successful */ }, function(error) { /* code if some error */ } );
function runPromise1() { var divResult = document.getElementById("results1"); let myPromise = new Promise(function(myResolve, myReject) { let x = 0; // THIS WOULD BE THE RESULT OF SOME ACTION if (x == 0) { myResolve("OK"); } else { myReject("Error"); } }); myPromise.then( function(value) {myDisplayer(value);}, function(error) {myDisplayer(error);} ); } function runPromise2() { var divResult = document.getElementById("results1"); let myPromise = new Promise(function(myResolve, myReject) { let x = 5; // THIS WOULD BE THE RESULT OF SOME ACTION if (x == 0) { myResolve("OK"); } else { myReject("Error"); } }); myPromise.then( function(value) {myDisplayer(value);}, function(error) {myDisplayer(error);} ); } function myDisplayer(strMessage) { document.getElementById("results1").innerHTML = strMessage; }

Clicking on the first button will run the script runPromise1() in which the result of the action where x = 0 causes the promise to call on the success function.

Clicking on the second button will run the script runPromise2() in which the result of the action where x = 5 causes the promise to call on the failure function.


Run With x = 0  Run With x = 5

Response


Sample

function runPromise3(fileName) { var divResult = document.getElementById("results3"); let myPromise = new Promise(function(myResolve, myReject) { let req = new XMLHttpRequest(); req.open('GET', fileName); req.onload = function() { if (req.status == 200) { myResolve(req.response); } else { myReject("File not Found"); } }; req.send(); }); myPromise.then( function(value) {myDisplayer3(value);}, function(error) {myDisplayer3(error);} ); } function myDisplayer3(some) { document.getElementById("results3").innerHTML = some; }

Sample With Existing HTML  Sample Without Existing HTML 

Response


Error Handling

const promise1 = new Promise((resolve, reject) => { // DO SOMETHING ASYNCHRONOUS try { // THIS WILL THROW AN ERROR 1 / 0; } catch (error) { reject(error); } }); promise1.then((value) => { // THIS WILL NEVER BE CALLED BECAUSE THE PROMISE WAS REJECTED // IT WILL ONLY BE CALLED IN A SUCCESSFUL OPERATION console.log(value); }, (error) => { // THIS WILL BE CALLED BECAUSE THE PROMISE WAS REJECTED console.log(error); });

In this code, the promise1 promise is created and rejected with an error. The then method is then used to handle the error. The success function will never be called because the promise was rejected. The failure function will be called and will log the error.

Here is another example of how to handle errors with promises:

const promise1 = new Promise((resolve, reject) => { // DO SOMETHING ASYNCHRONOUS fetch("https://example.com").then((response) => { if (response.status === 200) { resolve(response.json()); } else { reject(new Error("Error fetching data")); } }); }); promise1.then((data) => { // THIS WILL BE CALLED IF THE PROMISE WAS RESOLVED console.log(data); }, (error) => { // THIS WILL BE CALLED IF THE PROMISE WAS REJECTED console.log(error); });

In this code, the promise1 promise is created and used to fetch data from a web server.
The then method is then used to handle the success and failure cases.
The success function will be called if the request was successful and will log the data.
The failure function will be called if the request failed and will log the error.

Promises are a powerful tool for handling errors in JavaScript. They make it easier to write code that is more predictable and easier to read.


Multiple Async Calls

A real life example of using Javascript Promise for multiple asynchronous calls

// FETCH THE USER FROM THE DATABASE const user = await getUser(); // FETCH THE USER'S PROFILE IMAGE const profileImage = await getProfileImage(user.id); // UPLOAD THE PROFILE IMAGE TO THE SERVER await uploadProfileImage(profileImage); // SEND AN EMAIL TO THE USER CONFIRMING THE CHANGES await sendEmailConfirmation(user);

In this example, we are performing three asynchronous operations: fetching the user from the database, fetching the user's profile image, and uploading the profile image to the server. We can chain these operations together using promises, which ensures that they are executed in the correct order.


Async & Await

async/await and promises are closely related. async functions return promises, and await is waiting for a promise to be resolved. They are similar, but not the same.

async Function

The async function declaration creates a binding of a new async function to a given name. The await keyword is permitted within the function body, enabling asynchronous, promise-based behavior to be written in a cleaner style and avoiding the need to explicitly configure promise chains.

You can also define async functions using the async function expression.

function resolveAfter2Seconds() { return new Promise((resolve) => { setTimeout(() => { resolve("resolved"); }, 2000); }); } async function asyncCall() { console.log("calling"); const result = await resolveAfter2Seconds(); console.log(result); // Expected output: "resolved" } asyncCall();

Syntax

async function name(param0) { statements } async function name(param0, param1) { statements } async function name(param0, param1, /* …, */ paramN) { statements }

Parameters

nameThe function's name.
param (Optional)The name of a formal parameter for the function. For the parameters' syntax, see the Functions reference.
statements (Optional)The statements comprising the body of the function. The await mechanism may be used.

Description

An async function declaration creates an AsyncFunction object. Each time when an async function is called, it returns a new Promise which will be resolved with the value returned by the async function, or rejected with an exception uncaught within the async function.

Async functions can contain zero or more await expressions. Await expressions make promise-returning functions behave as though they're synchronous by suspending execution until the returned promise is fulfilled or rejected. The resolved value of the promise is treated as the return value of the await expression. Use of async and await enables the use of ordinary try / catch blocks around asynchronous code.

The await keyword is only valid inside async functions within regular JavaScript code. If you use it outside of an async function's body, you will get a SyntaxError.
 
await can be used on its own with JavaScript modules.

The purpose of async/await is to simplify the syntax necessary to consume promise-based APIs. The behavior of async/await is similar to combining generators and promises.

Async functions always return a promise. If the return value of an async function is not explicitly a promise, it will be implicitly wrapped in a promise.

For example, consider the following code:

async function foo() { return 1; }

It is similar to:

function foo() { return Promise.resolve(1); }

Note that even though the return value of an async function behaves as if it's wrapped in a Promise.resolve, they are not equivalent. An async function will return a different reference, whereas Promise.resolve returns the same reference if the given value is a promise. It can be a problem when you want to check the equality of a promise and a return value of an async function.

const p = new Promise((res, rej) => { res(1); }); async function asyncReturn() { return p; } function basicReturn() { return Promise.resolve(p); } console.log(p === basicReturn()); // true console.log(p === asyncReturn()); // false

The body of an async function can be thought of as being split by zero or more await expressions. Top-level code, up to and including the first await expression (if there is one), is run synchronously. In this way, an async function without an await expression will run synchronously. If there is an await expression inside the function body, however, the async function will always complete asynchronously.

For example:

async function foo() { await 1; }

It is also equivalent to:

function foo() { return Promise.resolve(1).then(() => undefined); }

Code after each await expression can be thought of as existing in a .then callback. In this way a promise chain is progressively constructed with each reentrant step through the function. The return value forms the final link in the chain.

In the following example, we successively await two promises. Progress moves through function foo in three stages.

  1. The first line of the body of function foo is executed synchronously, with the await expression configured with the pending promise. Progress through foo is then suspended and control is yielded back to the function that called foo.
  2. Some time later, when the first promise has either been fulfilled or rejected, control moves back into foo. The result of the first promise fulfillment (if it was not rejected) is returned from the await expression. Here 1 is assigned to result1. Progress continues, and the second await expression is evaluated. Again, progress through foo is suspended and control is yielded.
  3. Some time later, when the second promise has either been fulfilled or rejected, control re-enters foo. The result of the second promise resolution is returned from the second await expression. Here 2 is assigned to result2. Control moves to the return expression (if any). The default return value of undefined is returned as the resolution value of the current promise.
async function foo() { const result1 = await new Promise((resolve) => setTimeout(() => resolve("1")), ); const result2 = await new Promise((resolve) => setTimeout(() => resolve("2")), ); } foo();

Note how the promise chain is not built-up in one go. Instead, the promise chain is constructed in stages as control is successively yielded from and returned to the async function. As a result, we must be mindful of error handling behavior when dealing with concurrent asynchronous operations.

For example, in the following code an unhandled promise rejection error will be thrown, even if a .catch handler has been configured further along the promise chain. This is because p2 will not be "wired into" the promise chain until control returns from p1.

async function foo() { const p1 = new Promise((resolve) => setTimeout(() => resolve("1"), 1000)); const p2 = new Promise((_, reject) => setTimeout(() => reject("2"), 500)); const results = [await p1, await p2]; // Do not do this! Use Promise.all or Promise.allSettled instead. } foo().catch(() => {}); // Attempt to swallow all errors...

async function declarations behave similar to function declarations — they are hoisted to the top of their scope and can be called anywhere in their scope, and they can be redeclared only in certain contexts.

Examples

Async functions and execution order

function resolveAfter2Seconds() { console.log("starting slow promise"); return new Promise((resolve) => { setTimeout(() => { resolve("slow"); console.log("slow promise is done"); }, 2000); }); } function resolveAfter1Second() { console.log("starting fast promise"); return new Promise((resolve) => { setTimeout(() => { resolve("fast"); console.log("fast promise is done"); }, 1000); }); } async function sequentialStart() { console.log("== sequentialStart starts =="); // 1. Start a timer, log after it's done const slow = resolveAfter2Seconds(); console.log(await slow); // 2. Start the next timer after waiting for the previous one const fast = resolveAfter1Second(); console.log(await fast); console.log("== sequentialStart done =="); } async function sequentialWait() { console.log("== sequentialWait starts =="); // 1. Start two timers without waiting for each other const slow = resolveAfter2Seconds(); const fast = resolveAfter1Second(); // 2. Wait for the slow timer to complete, and then log the result console.log(await slow); // 3. Wait for the fast timer to complete, and then log the result console.log(await fast); console.log("== sequentialWait done =="); } async function concurrent1() { console.log("== concurrent1 starts =="); // 1. Start two timers concurrently and wait for both to complete const results = await Promise.all([ resolveAfter2Seconds(), resolveAfter1Second(), ]); // 2. Log the results together console.log(results[0]); console.log(results[1]); console.log("== concurrent1 done =="); } async function concurrent2() { console.log("== concurrent2 starts =="); // 1. Start two timers concurrently, log immediately after each one is done await Promise.all([ (async () => console.log(await resolveAfter2Seconds()))(), (async () => console.log(await resolveAfter1Second()))(), ]); console.log("== concurrent2 done =="); } sequentialStart(); // after 2 seconds, logs "slow", then after 1 more second, "fast" // wait above to finish setTimeout(sequentialWait, 4000); // after 2 seconds, logs "slow" and then "fast" // wait again setTimeout(concurrent1, 7000); // same as sequentialWait // wait again setTimeout(concurrent2, 10000); // after 1 second, logs "fast", then after 1 more second, "slow"

await and concurrency

In sequentialStart, execution suspends 2 seconds for the first await, and then another second for the second await. The second timer is not created until the first has already fired, so the code finishes after 3 seconds.

In sequentialWait, both timers are created and then awaited. The timers run concurrently, which means the code finishes in 2 rather than 3 seconds, i.e., the slowest timer. However, the await calls still run in series, which means the second await will wait for the first one to finish. In this case, the result of the fastest timer is processed after the slowest.

If you wish to safely perform other jobs after two or more jobs run concurrently and are complete, you must await a call to Promise.all() or Promise.allSettled() before that job.

Warning: The functions sequentialWait and concurrent1 are not functionally equivalent.
 
In sequentialWait, if promise fast rejects before promise slow is fulfilled, then an unhandled promise rejection error will be raised, regardless of whether the caller has configured a catch clause.
 
In concurrent1, Promise.all wires up the promise chain in one go, meaning that the operation will fail-fast regardless of the order of rejection of the promises, and the error will always occur within the configured promise chain, enabling it to be caught in the normal way.

Rewriting a Promise chain with an async function

An API that returns a Promise will result in a promise chain, and it splits the function into many parts. Consider the following code:

function getProcessedData(url) { return downloadData(url) // returns a promise .catch((e) => downloadFallbackData(url)) // returns a promise .then((v) => processDataInWorker(v)); // returns a promise }

it can be rewritten with a single async function as follows:

async function getProcessedData(url) { let v; try { v = await downloadData(url); } catch (e) { v = await downloadFallbackData(url); } return processDataInWorker(v); }

Alternatively, you can chain the promise with catch():

async function getProcessedData(url) { const v = await downloadData(url).catch((e) => downloadFallbackData(url)); return processDataInWorker(v); }

In the two rewritten versions, notice there is no await statement after the return keyword, although that would be valid too: The return value of an async function is implicitly wrapped in Promise.resolve - if it's not already a promise itself (as in the examples).


async function expression

The async function keywords can be used to define an async function inside an expression.

You can also define async functions using the async function declaration or the arrow syntax.

Syntax

async function (param0) { statements } async function (param0, param1) { statements } async function (param0, param1, /* …, */ paramN) { statements } async function name(param0) { statements } async function name(param0, param1) { statements } async function name(param0, param1, /* …, */ paramN) { statements }

An expression statement cannot begin with the keywords async function to avoid ambiguity with an async function declaration. The async function keywords only begin an expression when they appear in a context that cannot accept statements.

Parameters

name (Optional)The function name. Can be omitted, in which case the function is anonymous. The name is only local to the function body.
paramN (Optional)The name of a formal parameter for the function. For the parameters' syntax, see the Functions reference.
statements (Optional)The statements which comprise the body of the function.

Description

An async function expression is very similar to, and has almost the same syntax as, an async function declaration. The main difference between an async function expression and an async function declaration is the function name, which can be omitted in async function expressions to create anonymous functions. An async function expression can be used as an IIFE (Immediately Invoked Function Expression) which runs as soon as it is defined, allowing you to mimic top-level await. See also the chapter about functions for more information.

Examples

Using async function expression

function resolveAfter2Seconds(x) { return new Promise((resolve) => { setTimeout(() => { resolve(x); }, 2000); }); } // async function expression assigned to a variable const add = async function (x) { const a = await resolveAfter2Seconds(20); const b = await resolveAfter2Seconds(30); return x + a + b; }; add(10).then((v) => { console.log(v); // prints 60 after 4 seconds. }); // async function expression used as an IIFE (async function (x) { const p1 = resolveAfter2Seconds(20); const p2 = resolveAfter2Seconds(30); return x + (await p1) + (await p2); })(10).then((v) => { console.log(v); // prints 60 after 2 seconds. });

Async IIFE

An async IIFE allows you to use await and for...await in contexts where top-level await is not available. Here we use an arrow function to define the IIFE, but async function expressions can also be used.

const getFileStream = async (url) => { // implementation }; (async () => { const stream = await getFileStream("https://domain.name/path/file.ext"); for await (const chunk of stream) { console.log({ chunk }); } })();

await

The await operator is used to wait for a Promise and get its fulfillment value. It can only be used inside an async function or at the top level of a module.

await expression

Parameters

expressionA Promise, a thenable object, or any value to wait for.

Return value

The fulfillment value of the promise or thenable object, or, if the expression is not thenable, the expression's own value.

Exceptions

Throws the rejection reason if the promise or thenable object is rejected.

Description

await is usually used to unwrap promises by passing a Promise as the expression. Using await pauses the execution of its surrounding async function until the promise is settled (that is, fulfilled or rejected). When execution resumes, the value of the await expression becomes that of the fulfilled promise.

If the promise is rejected, the await expression throws the rejected value. The function containing the await expression will appear in the stack trace of the error. Otherwise, if the rejected promise is not awaited or is immediately returned, the caller function will not appear in the stack trace.

The expression is resolved in the same way as Promise.resolve(): it's always converted to a native Promise and then awaited. If the expression is a:

Even when the used promise is already fulfilled, the async function's execution still pauses until the next tick. In the meantime, the caller of the async function resumes execution. See example below.

Because await is only valid inside async functions and modules, which themselves are asynchronous and return promises, the await expression never blocks the main thread and only defers execution of code that actually depends on the result, i.e., anything after the await expression.

Examples

Awaiting a promise to be fulfilled

If a Promise is passed to an await expression, it waits for the Promise to be fulfilled and returns the fulfilled value.

function resolveAfter2Seconds(x) { return new Promise((resolve) => { setTimeout(() => { resolve(x); }, 2000); }); } async function f1() { const x = await resolveAfter2Seconds(10); console.log(x); // 10 } f1();

Thenable objects

Thenable objects are resolved just the same as actual Promise objects.

async function f2() { const thenable = { then(resolve) { resolve("resolved!"); }, }; console.log(await thenable); // "resolved!" } f2();

They can also be rejected:

async function f2() { const thenable = { then(_, reject) { reject(new Error("rejected!")); }, }; await thenable; // Throws Error: rejected! } f2();

Conversion to promise

If the value is not a Promise, await converts the value to a resolved Promise, and waits for it. The awaited value's identity doesn't change as long as it doesn't have a then property that's callable.

async function f3() { const y = await 20; console.log(y); // 20 const obj = {}; console.log((await obj) === obj); // true } f3();

Handling rejected promises

If the Promise is rejected, the rejected value is thrown.

async function f4() { try { const z = await Promise.reject(30); } catch (e) { console.error(e); // 30 } } f4();

You can handle rejected promises without a try block by chaining a catch() handler before awaiting the promise.

const response = await promisedFunction().catch((err) => { console.error(err); return "default response"; }); // response will be "default response" if the promise is rejected

This is built on the assumption that promisedFunction() never synchronously throws an error, but always returns a rejected promise. This is the case for most properly-designed promise-based functions, which usually look like:

function promisedFunction() { // Immediately return a promise to minimize chance of an error being thrown return new Promise((resolve, reject) => { // do something async }); }

However, if promisedFunction() does throw an error synchronously, the error won't be caught by the catch() handler. In this case, the try...catch statement is necessary.

Top level await

You can use the await keyword on its own (outside of an async function) at the top level of a module. This means that modules with child modules that use await will wait for the child modules to execute before they themselves run, all while not blocking other child modules from loading.

Here is an example of a module using the Fetch API and specifying await within the export statement. Any modules that include this will wait for the fetch to resolve before running any code.

// fetch request const colors = fetch("../data/colors.json").then((response) => response.json()); export default await colors;

Control flow effects of await

When an await is encountered in code (either in an async function or in a module), the awaited expression is executed, while all code that depends on the expression's value is paused. Control exits the function and returns to the caller. When the awaited expression's value is resolved, another microtask that continues the paused code gets scheduled. This happens even if the awaited value is an already-resolved promise or not a promise: execution doesn't return to the current function until all other already-scheduled microtasks are processed. For example, consider the following code:

async function foo(name) { console.log(name, "start"); console.log(name, "middle"); console.log(name, "end"); } foo("First"); foo("Second"); // First start // First middle // First end // Second start // Second middle // Second end

In this case, the function foo is synchronous in effect, because it doesn't contain any await expression. The three statements happen in the same tick. Therefore, the two function calls execute all statements in sequence. In promise terms, the function corresponds to:

function foo(name) { return new Promise((resolve) => { console.log(name, "start"); console.log(name, "middle"); console.log(name, "end"); resolve(); }); }

However, as soon as there's one await, the function becomes asynchronous, and execution of following statements is deferred to the next tick.

async function foo(name) { console.log(name, "start"); await console.log(name, "middle"); console.log(name, "end"); } foo("First"); foo("Second"); // First start // First middle // Second start // Second middle // First end // Second end

This corresponds to:

function foo(name) { return new Promise((resolve) => { console.log(name, "start"); resolve(console.log(name, "middle")); }).then(() => { console.log(name, "end"); }); }

The extra then() handler can be merged with the executor passed to the constructor because it's not waiting on any asynchronous operation. However, its existence splits the code into one additional microtask for each call to foo. These microtasks are scheduled and executed in an intertwined manner, which can both make your code slower and introduce unnecessary race conditions. Therefore, make sure to use await only when necessary (to unwrap promises into their values).

Microtasks are scheduled not only by promise resolution but by other web APIs as well, and they execute with the same priority. This example uses queueMicrotask() to demonstrate how the microtask queue is processed when each await expression is encountered.

let i = 0; queueMicrotask(function test() { i++; console.log("microtask", i); if (i < 3) { queueMicrotask(test); } }); (async () => { console.log("async function start"); for (let i = 1; i < 3; i++) { await null; console.log("async function resume", i); } await null; console.log("async function end"); })(); queueMicrotask(() => { console.log("queueMicrotask() after calling async function"); }); console.log("script sync part end"); // Logs: // async function start // script sync part end // microtask 1 // async function resume 1 // queueMicrotask() after calling async function // microtask 2 // async function resume 2 // microtask 3 // async function end

In this example, the test() function is always called before the async function resumes, so the microtasks they each schedule are always executed in an intertwined fashion. On the other hand, because both await and queueMicrotask() schedule microtasks, the order of execution is always based on the order of scheduling. This is why the "queueMicrotask() after calling async function" log happens after the async function resumes for the first time.

Improving stack trace

Sometimes, the await is omitted when a promise is directly returned from an async function.

async function noAwait() { // Some actions... return /* await */ lastAsyncTask(); }

However, consider the case where lastAsyncTask asynchronously throws an error.

async function lastAsyncTask() { await null; throw new Error("failed"); } async function noAwait() { return lastAsyncTask(); } noAwait(); // Error: failed // at lastAsyncTask

Only lastAsyncTask appears in the stack trace, because the promise is rejected after it has already been returned from noAwait — in some sense, the promise is unrelated to noAwait. To improve the stack trace, you can use await to unwrap the promise, so that the exception gets thrown into the current function. The exception will then be immediately wrapped into a new rejected promise, but during error creation, the caller will appear in the stack trace.

async function lastAsyncTask() { await null; throw new Error("failed"); } async function withAwait() { return await lastAsyncTask(); } withAwait(); // Error: failed // at lastAsyncTask // at async withAwait

Contrary to some popular belief, return await promise is at least as fast as return promise, due to how the spec and engines optimize the resolution of native promises. There's a proposal to make return promise faster and you can also read about V8's optimization on async functions. Therefore, except for stylistic reasons, return await is almost always preferable.


The Pyramid of Doom

A common need is to execute two or more asynchronous operations back to back, where each subsequent operation starts when the previous operation succeeds, with the result from the previous step.
In the old days, doing several asynchronous operations in a row would lead to the classic callback pyramid of doom:

/// THE PYRAMID OF DOOM doSomething(function (result) { doSomethingElse(result, function (newResult) { doThirdThing(newResult, function (finalResult) { console.log('Got the final result: ${finalResult}'); }, failureCallback); }, failureCallback); }, failureCallback);

Chaining

With promises, we accomplish this by creating a promise chain.
The API design of promises makes this great, because callbacks are attached to the returned promise object, instead of being passed into a function.

Here's the magic: the then() function returns a new promise, different from the original:

const promise = doSomething(); const promise2 = promise.then(successCallback, failureCallback);

This second promise promise2 represents the completion not just of doSomething(), but also of the successCallback or failureCallback you passed in, which can be other asynchronous functions returning a promise. When that's the case, any callbacks added to promise2 get queued behind the promise returned by either successCallback or failureCallback.

With this pattern, you can create longer chains of processing, where each promise represents the completion of one asynchronous step in the chain. In addition, the arguments to then are optional, and catch(failureCallback) is short for then(null, failureCallback), so if your error handling code is the same for all steps, you can attach it to the end of the chain:

Longer Chains of Processing

doSomething() .then(function (result) { return doSomethingElse(result); }) .then(function (newResult) { return doThirdThing(newResult); }) .then(function (finalResult) { console.log('Got the final result: ${finalResult}'); }) .catch(failureCallback);

Arrow Functions

This can also be expressed using arrow functions

doSomething() .then((result) => doSomethingElse(result)) .then((newResult) => doThirdThing(newResult)) .then((finalResult) => { console.log('Got the final result: ${finalResult}'); }) .catch(failureCallback);

The Floating Promise Problem

Important Note: Always return results, otherwise callbacks won't catch the result of a previous promise (with arrow functions, () => x is short for () => { return x; }). If the previous handler started a promise but did not return it, there's no way to track its settlement anymore, and the promise is said to be "floating".

doSomething() .then((url) => { // I forgot to return this fetch(url); }) .then((result) => { // RESULT IS UNDEFINED, BECAUSE NOTHING IS RETURNED FROM // THE PREVIOUS HANDLER. // THERE'S NO WAY TO KNOW THE RETURN VALUE OF THE fetch() // CALL ANYMORE, OR WHETHER IT SUCCEEDED AT ALL. });


Race Conditions

This may be worse if you have race conditions, if the promise from the last handler is not returned, the next then handler will be called early, and any value it reads may be incomplete.

const listOfIngredients = []; doSomething() .then((url) => { // I FORGOT TO RETURN THIS fetch(url) .then((res) => res.json()) .then((data) => { listOfIngredients.push(data); }); }) .then(() => { console.log(listOfIngredients); // ALWAYS [], BECAUSE THE FETCH REQUEST HASN'T COMPLETED YET. });

Therefore, as a rule of thumb, whenever your operation encounters a promise, return it and defer its handling to the next then handler.

const listOfIngredients = []; doSomething() .then((url) => fetch(url) .then((res) => res.json()) .then((data) => { listOfIngredients.push(data); }), ) .then(() => { console.log(listOfIngredients); }); // OR doSomething() .then((url) => fetch(url)) .then((res) => res.json()) .then((data) => { listOfIngredients.push(data); }) .then(() => { console.log(listOfIngredients); });

Nesting

In the two examples above, the first one has one promise chain nested in the return value of another then() handler, while the second one uses an entirely flat chain. Simple promise chains are best kept flat without nesting, as nesting can be a result of careless composition. See common mistakes.

Nesting is a control structure to limit the scope of catch statements. Specifically, a nested catch only catches failures in its scope and below, not errors higher up in the chain outside the nested scope. When used correctly, this gives greater precision in error recovery:

doSomethingCritical() .then((result) => doSomethingOptional(result) .then((optionalResult) => doSomethingExtraNice(optionalResult)) .catch((e) => {}), ) // IGNORE IF OPTIONAL STUFF FAILS; PROCEED. .then(() => moreCriticalStuff()) .catch((e) => console.error('Critical failure: ${e.message}'));

Note that the optional steps here are nested, with the nesting caused not by the indentation, but by the placement of the outer ( and ) parentheses around the steps.

The inner error-silencing catch handler only catches failures from doSomethingOptional() and doSomethingExtraNice(), after which the code resumes with moreCriticalStuff().
Importantly, if doSomethingCritical() fails, its error is caught by the final (outer) catch only, and does not get swallowed by the inner catch handler.

Chaining After A Catch

It's possible to chain after a failure, i.e. a catch, which is useful to accomplish new actions even after an action failed in the chain. Read the following example:

new Promise((resolve, reject) => { console.log("Initial"); resolve(); }) .then(() => { throw new Error("Something failed"); console.log("Do this"); }) .catch(() => { console.error("Do that"); }) .then(() => { console.log("Do this, no matter what happened before"); });

This will output the following text:

Initial Do that Do this, no matter what happened before

Chained Promise

Response


function runPromise1() { var divResult = document.getElementById("results1"); new Promise((resolve, reject) => { divResult.innerHTML = "Initial"; resolve(); }) .then(() => { throw new Error("Something failed"); divResult.innerHTML += "<br/>Do this"; }) .catch(() => { divResult.innerHTML += "<br/>Do that"; }) .then(() => { divResult.innerHTML += "<br/>Do this, no matter what happened before"; }); } // RESPONSE Initial Do that Do this, no matter what happened before

Code Trace


Working Example 1

/******************************************************************************/ function resolveAfter2Seconds() { console.log("starting slow promise"); outputDiv1.innerHTML += "Starting Slow Promise - resolveAfter2Seconds()<br/>"; return new Promise((resolve) => { setTimeout(() => { resolve("slow"); console.log("slow promise is done"); outputDiv1.innerHTML += "Slow Promise Is Done - resolveAfter2Seconds()<br/>"; }, 2000); }); } /******************************************************************************/ function resolveAfter1Second() { console.log("starting fast promise"); outputDiv1.innerHTML += "Starting Fast Promise - resolveAfter1Second()<br/>"; return new Promise((resolve) => { setTimeout(() => { resolve("fast"); console.log("fast promise is done"); outputDiv1.innerHTML += "Fast Promise Is Done - resolveAfter1Second()<br/>"; }, 1000); }); } /******************************************************************************/ async function sequentialStart() { console.log("== sequentialStart starts =="); outputDiv1.innerHTML += "** sequentialStart() Starts **<br/>"; const slow = resolveAfter2Seconds(); // 1. START A TIMER, LOG AFTER IT'S DONE console.log(await slow); outputDiv1.innerHTML += "Slow - sequentialStart()<br/>"; const fast = resolveAfter1Second(); // 2. START THE NEXT TIMER AFTER WAITING FOR THE PREVIOUS ONE console.log(await fast); outputDiv1.innerHTML += "fast<br/>"; console.log("== sequentialStart done =="); outputDiv1.innerHTML += "** sequentialStart() Ends **<br/>"; } /******************************************************************************/ async function sequentialWait() { console.log("== sequentialWait starts =="); outputDiv1.innerHTML += "** sequentialWait() Starts **<br/>"; const slow = resolveAfter2Seconds(); // 1. START TWO TIMERS WITHOUT WAITING FOR EACH OTHER const fast = resolveAfter1Second(); console.log(await slow); // 2. WAIT FOR THE SLOW TIMER TO COMPLETE, AND THEN LOG THE RESULT outputDiv1.innerHTML += "Slow - sequentialWait()<br/>"; console.log(await fast); // 3. WAIT FOR THE FAST TIMER TO COMPLETE, AND THEN LOG THE RESULT outputDiv1.innerHTML += "Fast - sequentialWait()<br/>"; console.log("== sequentialWait done =="); outputDiv1.innerHTML += "** sequentialWait() Ends **<br/>"; } /******************************************************************************/ async function concurrent1() { console.log("== concurrent1 starts =="); outputDiv1.innerHTML += "** concurrent1() Starts **<br/>"; // 1. START TWO TIMERS CONCURRENTLY AND WAIT FOR BOTH TO COMPLETE const results = await Promise.all([ resolveAfter2Seconds(), resolveAfter1Second(), ]); // 2. LOG THE RESULTS TOGETHER console.log(results[0]); outputDiv1.innerHTML += "results[0]: "+ results[0] +"<br/>"; console.log(results[1]); outputDiv1.innerHTML += "results[1]: "+ results[1] +"<br/>"; console.log("== concurrent1 done =="); outputDiv1.innerHTML += "** concurrent1() Ends **<br/>"; } /******************************************************************************/ async function concurrent2() { console.log("== concurrent2 starts =="); outputDiv1.innerHTML += "** concurrent2 Starts **<br/>"; // 1. START TWO TIMERS CONCURRENTLY, LOG IMMEDIATELY AFTER EACH ONE IS DONE await Promise.all([ (async () => console.log(await resolveAfter2Seconds()))(), (async () => console.log(await resolveAfter1Second()))(), ]); console.log("== concurrent2 done =="); outputDiv1.innerHTML += "** concurrent2 Ends **<br/>"; } let runExample1 = function() { outputDiv1.innerHTML = "Starting Example - Calling sequentialStart()<br/>"; sequentialStart(); // AFTER 2 SECONDS, LOGS "slow", THEN AFTER 1 MORE SECOND, "fast" // WAIT ABOVE TO FINISH outputDiv1.innerHTML += "After 4000 Calling sequentialWait()<br/>"; setTimeout(sequentialWait, 4000); // AFTER 2 SECONDS, LOGS "slow" AND THEN "fast" // WAIT AGAIN outputDiv1.innerHTML += "After 7000 Calling concurrent1()<br/>"; setTimeout(concurrent1, 7000); // SAME AS sequentialWait // WAIT AGAIN outputDiv1.innerHTML += "After 10000 Calling concurrent2()<br/>"; setTimeout(concurrent2, 10000); // AFTER 1 SECOND, LOGS "fast", THEN AFTER 1 MORE SECOND, "slow" };

Run Example 1

Response 1


Working Example 2

const userLeft = true; const userWatchingCatMeme = false; function watchTutorialCallback(callback, errorCallback) { if (userLeft) { errorCallback({ name: "User Left", message: "No one is home"}); } else if (userWatchingCatMeme) { errorCallback({ name: "User Watching Cat Meme", message: "User waisting time"}); } else { callback("User ready to go"); } } let runExample2 = function() { outputDiv2.innerHTML = "Starting<br/>"; watchTutorialCallback((message) => { console.log("Success: "+ message); outputDiv2.innerHTML = "Success: "+ message +"<br/>"; }, (error) => { console.log(error.name +" "+ error.message); outputDiv2.innerHTML = "Error: "+ error.name +" - "+ error.message +"<br/>"; }) };

Run Example 2

Response 2



Working Example 3

function watchTutorialPromise() { return new Promise((resolve, reject) => { if (userLeft) { reject({ name: "User Left", message: "No One Is Home"}); } else if (userWatchingCatMeme) { reject({ name: "User Watching Cat Meme", message: "User Waisting Time"}); } else { resolve("User Ready To Go"); } }) } let runExample3 = function() { outputDiv3.innerHTML = "Starting<br/>"; watchTutorialPromise().then((message) => { console.log("Success: "+ message); outputDiv3.innerHTML = "Success: "+ message; }).catch((error) => { console.log(error.name +" "+ error.message); outputDiv3.innerHTML = "Error: "+ error.name +" "+ error.message; }) };

Run Example 3

Response 3


Working Example 4

In this example the Promise method all() is called. This processes all items in the given array.
There is another method called race() which will only process the item which finishes first.

const recordVideoOne = new Promise((resolve, reject) => { resolve('Video 1 Recorded'); }) const recordVideoTwo = new Promise((resolve, reject) => { resolve('Video 2 Recorded'); }) const recordVideoThree = new Promise((resolve, reject) => { resolve('Video 3 Recorded'); }) let runExample4 = function() { outputDiv4.innerHTML = "Starting<br/>"; Promise.all([ recordVideoOne, recordVideoTwo, recordVideoThree ]).then((messages) => { console.log(messages); outputDiv4.innerHTML += "messages: "+ messages +"<br/>"; }) };
Run Example 4

Response 4