Asynchronous JavaScript Programming and Callbacks
Asynchronous (async) means "not synchronous".
An event is asynchronous if it does not wait for another event to complete its processing before starting its own execution.
In other words, an asynchronous function is a function that runs in its own timing—without waiting for another function to finish its execution first.
The setTimeout()
method is a typical example of an async function because it invokes its callback argument in a given time—irrespective of the ongoing execution of other functions.
But what exactly is the difference between an asynchronous event and a synchronous one? Let's find out.
Asynchronous vs. Synchronous Events – What's the Difference?
An asynchronous event is a program that can happen at any convenient time.
A synchronous event is an activity that must occur at a specific point in time.
In other words, an asynchronous event permits functions to happen independently—without waiting in line for the completion of other programs.
However, a synchronous event requires functions to happen one after the other. So, function B must wait in line for function A to complete its execution before commencing its own.
Synchronous programming most often affects an app's usability. Because users usually have to stare at the spinning cursor—waiting for a function to finish its processing before the program can become usable again.
Blocking is a term used to refer to the spinning cursor moments (when the browser appears frozen). In such a period, the currently running operation blocks the browser from doing other things until it has finished its execution.
On the other hand, asynchronous programming allows users to do other things while waiting for another function to finish its processing.
But how exactly does JavaScript implement asynchronous programming? Let's find out below.
How to Implement Asynchronous Programming in JavaScript
JavaScript, by default, is a single-threaded programming language. However, modern browsers allow us to implement asynchronous programming in the following two ways:
- Passing callbacks into functions.
- Attaching callbacks to promise objects.
But what exactly is a callback, and what does a promise mean? Let's find out.
What Is a Callback?
A callback is a function A passed as an argument into a function B and invoked inside function B.
In other words, after passing function A into function B, we expect function B to callback (invoke) function A at a given time.
- Callback functions are sometimes called call-after functions.
- The specific time function B will callback function A may be immediately or later in the future.
- If the callback's execution happens immediately, the call-after is called a synchronous callback.
- If the callback's invocation occurs at some later time, the call-after is called an asynchronous callback.
Let's see some examples of a synchronous and asynchronous callback function.
Example 1: Synchronous callback function
In the snippet below, bestColor()
is a callback function because:
- We passed it as an argument to the
showBestColor()
function - We invoked it inside
showBestColor()
function bestColor(color) {
console.log("My best color is " + color + ".");
}
function showBestColor(callback, choice) {
callback(choice);
}
showBestColor(bestColor, "White");
// The invocation above will return: "My best color is White."
The bestColor()
callback function above is a synchronous callback because we programmed its execution to happen immediately.
Let's now see an example of an asynchronous callback function.
Example 2: Asynchronous callback function
In the snippet below, bestColor()
is a callback function because:
- We passed it as an argument to the
setTimeout()
function - The
bestColor()
function got invoked insidesetTimeout()
function bestColor(color) {
console.log("My best color is " + color + ".");
}
setTimeout(bestColor, 3000, "White");
// The invocation above will return: "My best color is White."
The bestColor()
callback is an asynchronous callback because we programmed its execution to happen at a given time—after 3000
milliseconds (3 seconds).
Use Flexbox like a pro
Keep in mind that an alternate way to write the code above is like so:
setTimeout(
function (color) {
console.log("My best color is " + color + ".");
},
3000,
"White"
);
// The invocation above will return: "My best color is White."
In the snippet above, we passed a complete function—not a reference—as a callback argument to the setTimeout()
method.
Now, beware that we implemented the "inversion of control (IoC)" programming principle in the asynchronous callback snippets above.
But what exactly is "inversion of control," I hear you ask? Let's find out below.
What Is Inversion of Control?
Inversion of control (IoC) occurs when you transfer the control of your code's execution to a third party.
The IoC programming principle commonly happens while writing a callback program—where you provide your function as an argument to a third-party's function.
For instance, consider the code below:
function bestColor(color) {
console.log("My best color is " + color + ".");
}
setTimeout(bestColor, 3000, "White");
In the snippet above, setTimeout()
is a third-party code that belongs to the browser's Window
object.
On the other hand, bestColor()
is our own code (a code we wrote ourselves).
Now, since we passed our code (bestColor()
) as an argument to a third party's code (setTimeout()
). It implies that we've transferred the control of our function's execution to setTimeout()
.
In other words, we inverted the control of bestColor()
's execution from ourselves to the third party.
Therefore, setTimeout()
has full control to determine how bestColor()
will get executed.
Inversion of control could be problematic if you do not design your code with logic to mitigate trust issues.
So, now that we know what callbacks are, we can talk about promises.
What Is a Promise?
A JavaScript promise refers to the object you create from JavaScript's built-in constructor function—called Promise
.
Here's an example:
new Promise(function () { });
// The invocation above will return:
Promise { <state>: "pending" }
In the snippet above, we created a promise object by passing a callback function into JavaScript's built-in Promise
constructor function.
Keep in mind that a callback function is a required argument of the Promise
constructor function. If omitted, JavaScript will throw an Uncaught TypeError
.
Here's an example:
new Promise();
// The invocation above will return: "Uncaught TypeError: undefined is not a function"
Promise's state
A promise is always in one of three states: pending
, fulfilled
, or rejected
.
When you have a pending promise, it means you have an unfulfilled or unrejected promise.
In other words, a pending promise means you have not received any response concerning a specific promise.
Here's an example:
const myMomsPromise = new Promise(function () { });
console.log(myMomsPromise);
// The invocation above will return:
Promise { <state>: "pending" }
Suppose a promise returns a valid value. In that case, the promise's state will switch from its pending
state to a fulfilled
state.
Here's an example:
const myMomsPromise = new Promise(function (resolve, reject) {
resolve();
});
console.log(myMomsPromise);
// The invocation above will return:
Promise { <state>: "fulfilled", <value>: undefined }
Now, suppose a promise could not return a valid value. In that case, the promise's state will change from its pending
state to a rejected
state.
Here's an example:
const myMomsPromise = new Promise(function (resolve, reject) {
reject();
});
console.log(myMomsPromise);
// The invocation above will return:
Promise { <state>: "rejected", <reason>: undefined }
Turn your passion into a business
Important stuff to know about promises
Keep these three essential pieces of info in mind whenever you choose to use promises.
-
A promise is settled if it is either
fulfilled
orrejected
. -
A settled promise is sometimes called a resolved (or locked in) promise.
-
resolve
—notfulfill
—is the recommended name for the first parameter of aPromise
constructor's callback argument. Developers recommend "resolve" because the first parameter does not always set a value to a fulfilled promise. In other words, the first parameter can resolve a promise object to a rejected state.Here's an example:
const myMomsPromise = new Promise(function (resolve, reject) {
resolve(Promise.reject());
});
console.log(myMomsPromise);
// The invocation above will return:
Promise { <state>: "rejected", <reason>: undefined }
So, now that we know what promises and callbacks are, let's continue our discussion on the two ways to implement asynchronous programming in JavaScript.
Remember, we said the two typical ways to implement asynchronous programming in JavaScript are:
- By passing callbacks into functions, and
- By attaching callbacks to a Promise object.
Let's see how the two techniques work.
How to Implement Async Programming by Passing Callbacks into Functions
Suppose your mom promised you some exceptional gifts if you pass your exam.
In such a case, you can represent mom's promise and its eventual settlement (fulfillment or rejection) by passing callbacks into functions like so:
// Create a variable:
const passedExam = true;
// Create a function that return mom's first promise:
function emitFirstProm() {
return "I get a book";
}
// Create a function that add mom's first promise to her second one:
function emitSecondProm(firstPromise, addFirstPromToSec) {
addFirstPromToSec(firstPromise() + ", a phone", passedExam);
}
// Create a function that add mom's previous promises to her third one:
function emitThirdProm(pastProms, addPastPromsToThird) {
addPastPromsToThird(pastProms + ", a laptop", passedExam);
}
// Create a function that add mom's previous promises to her fourth one:
function emitFourthProm(pastProms, addPastPromsToFourth) {
addPastPromsToFourth(pastProms + ", and a Jeep 🤩🎉✨", passedExam);
}
// Invoke each of the functions above asynchronously by using them as callback arguments:
emitSecondProm(emitFirstProm, function (pastPromPlusSec, IPassedMyExam) {
let finalResponse = null;
if (IPassedMyExam) {
emitThirdProm(
pastPromPlusSec,
function (pastPromsPlusThird, IPassedMyExam) {
if (IPassedMyExam) {
emitFourthProm(
pastPromsPlusThird,
function (pastPromsPlusForth, IPassedMyExam) {
if (IPassedMyExam) {
finalResponse = `Mom fulfilled her promise! ${pastPromsPlusForth}`;
}
}
);
}
}
);
} else {
console.error(
"Oops! Caught the following error: You did not pass your exam, so mom rejected her promise."
);
}
console.log(finalResponse);
});
// The invocation above will return:
// "Mom fulfilled her promise! I get a book, a phone, a laptop, and a Jeep 🤩🎉✨"
Here's what we did in the snippet above:
- We created a variable (
passedExam
) that confirms if you passed your exam (true
). - We created a function (
emitFirstProm
) that—when invoked—will return Mom's first promise. - Thirdly, we created three other functions that—when invoked—will add mom's previous promise(s) to her latest one.
- Finally, we invoked each of the three functions asynchronously depending on whether
passedExam
's value istrue
orfalse
.
Therefore, since passedExam
's value is always true
in our example above, all mom's promises will be fulfilled. As such, our callbacks will return "Mom fulfilled her promise! I get a book, a phone, a laptop, and a Jeep 🤩🎉✨"
.
Passing callbacks into functions—as we did above—was the default way to accomplish several asynchronous operations, which often leads to creating the "callback pyramid of doom."
However, the modern invention of promises provides a neater way to implement asynchronous programming. Let's see how.
Sell everywhere
How to Implement Async Programming by Attaching Callbacks to a Promise Object
Suppose your mom promised you some exceptional gifts if you pass your exam.
In such a case, we can represent mom's promise and its eventual settlement (fulfillment or rejection) by attaching callbacks to a promise object like so:
// Create two variables:
let finalResponse = null;
const passedExam = true;
// Create a function that return mom's first promise:
function emitFirstProm() {
return "I get a book";
}
// Create a function that add mom's first promise to her second one:
function emitSecondProm(firstPromise) {
return firstPromise + ", a phone";
}
// Create a function that add mom's previous promises to her third one:
function emitThirdProm(pastProms) {
return pastProms + ", a laptop";
}
// Create a function that add mom's previous promises to her fourth one:
function emitFourthProm(pastProms) {
return pastProms + ", and a Jeep 🤩🎉✨";
}
// Create mom's promise:
const myMomsPromise = new Promise(function (resolve, reject) {
if (passedExam) {
resolve(emitFirstProm());
} else {
reject("You did not pass your exam, so mom rejected her promise.");
}
});
// Invoke each of the functions above asynchronously by attaching them to myMomsPromise object:
myMomsPromise
.then(emitSecondProm)
.then(emitThirdProm)
.then(emitFourthProm)
.then(function (value) {
finalResponse = `Mom fulfilled her promise! ${value}`;
})
.catch(function (e) {
console.error(`Oops! Caught the following error: ${e}`);
})
.finally(function () {
console.log(finalResponse);
});
// The invocation above will return:
// "Mom fulfilled her promise! I get a book, a phone, a laptop, and a Jeep 🤩🎉✨"
Here's what we did in the snippet above:
- We created a variable (
passedExam
) that confirms if you passed your exam (true
). - We created a function (
emitFirstProm
) that—when invoked—will return Mom's first promise. - Thirdly, we created three other functions that—when invoked—will add mom's previous promise(s) to her latest one.
- Fourthly, we created a promise object.
- Finally, we used the promise object's built-in methods—
then()
andcatch()
—to attach additional callback functions to the promise object. Then, depending on whetherpassedExam
's value istrue
orfalse
, the computer will invoke each attached function asynchronously.
Therefore, since passedExam
's value is always true
in the example above, all mom's promises will be fulfilled. As such, our callbacks will return "Mom fulfilled her promise! I get a book, a phone, a laptop, and a Jeep 🤩🎉✨"
.
14 Important Stuffs to Know about JavaScript's then()
, catch()
, and finally()
Methods
Keep these essential pieces of info in mind whenever you choose to use JavaScript's then()
, catch()
, and finally()
methods.
- Under the hood, each
then()
method returns a new promise object that is different from the original promise we assigned to themyMomsPromise
variable. - A promise object's
then()
method gets invoked when the promise chained to it is settled (fulfilled or rejected). - A promise's
then()
method will not get invoked if the promise chained to it ispending
. - A
then()
method's callback argument won't run immediately. It gets invoked in the future—when thethen()
method receives a settled response. - In a
then
chain, eachthen()
method gets the returned value of the previousthen()
method. - A
then()
method's callback argument needs to return a value. Otherwise, the subsequentthen()
method will not receive the promise object created by the precedingthen()
method. - A
then()
method accepts two optional arguments: asuccessCallback
function and afailureCallback
function. For instance,then(successCallback, failureCallback)
. - An alternate way of writing
then(null, failureCallback)
iscatch(failureCallback)
. - You can use a single
catch()
method to handle all errors in the precedingthen()
methods. - You can chain additional callbacks to a
catch()
method. In other words, suppose you intend to perform a new task after a rejected promise. In that case, you can still chain newthen()
methods to acatch()
method like so:
myMomsPromise
.then(successCallBack)
.catch(failureCallback)
.then(successCallBack);
- A
catch()
callback—which also returns aPromise
—gets executed when the promise object chained to it is in therejected
state. - The promise object returned by
then()
andcatch()
makes the methods chainable. - A
finally()
callback gets executed, regardless of the success or failure of the promise object to which it is chained. - Although
then()
is an inbuilt method of aPromise
constructor, however, anyone can create one. Therefore, watch out forthen()
methods that are not inbuilt methods of thePromise
constructor object. For instance, consider this example:
// Create a method named then:
const myThenMethod = {
then: function () {
console.log("I am a thenable object!");
},
};
// Invoke myThenMethod's then() method:
myThenMethod.then();
// The invocation above will return: "I am a thenable object!"
In the snippet above, myThenMethod
is a thenable object but not a promise.
Thenable in JavaScript refers to any object that contains a then()
method. Therefore, all promises are thenables. However, not all thenables are promises.
So, now that we know how to implement asynchronous programs in JavaScript, let's discuss some of the few advantages you will get by choosing to attach callbacks to a promise object.
Turn your passion into a business
Passing Callbacks into Functions vs. Attaching Callbacks to a Promise Object – Which Is the Best Way to Implement Asynchronous Programming?
You can see in the previous examples that passing callbacks into functions is barely readable—especially if you have numerous callbacks.
You are bound to create the callback pyramid of doom whenever you pass callbacks into functions—thereby making your codebase strenuous to read and debug.
Therefore, suppose you choose to implement async programming by attaching callbacks to your promise object. In that case, the main advantages you will benefit from are:
- Your code will be readable and straightforward.
- Your code will be easier to maintain and debug
Let's now discuss some of the Promise
constructor's static methods that you can use to extend its functionality.
The Promise Constructor's Static Methods
The Promise
constructor has some static methods that you can use to extends it functionality. Some of those methods are resolve()
, reject()
, all()
, allSettled()
, any()
, and race()
.
Let's discuss each one, starting with resolve()
.
Promise.resolve(...)
Promise.resolve()
returns a new promise object that resolves with the value passed as an argument to the resolve()
method.
Keep in mind that Promise.resolve()
is a simpler alternative to the new Promise(...)
syntax—especially when you do not need to use the Promise
constructor's function argument.
For instance, here's one way you can resolve a promise object to the "I will code sweetly!"
string value.
const myPromise = new Promise(function (resolve, reject) {
resolve("I will code sweetly!");
});
console.log(myPromise);
// The invocation above will return:
Promise { <state>: "fulfilled", <value>: "I will code sweetly!" }
A simpler way of writing the snippet above is by using Promise.resolve()
like so:
const myPromise = Promise.resolve("I will code sweetly!");
console.log(myPromise);
// The invocation above will return:
Promise { <state>: "fulfilled", <value>: "I will code sweetly!" }
Important stuff to know about Promise.resolve()
Keep these two essential pieces of info in mind whenever you choose to use Promise.resolve()
.
Info 1: Promise.resolve()
's argument determines the resolved value
A promise object's resolved value depends on the data type you passed as an argument to the resolve()
method.
For instance, the promise object in the snippet below resolved to an array value because we passed in an array data type as an argument to the resolve()
method.
const myPromise = Promise.resolve(["I will code sweetly"]);
console.log(myPromise);
// The invocation above will return:
Promise { <state>: "fulfilled", <value>: Array ["I will code sweetly!"] }
Info 2: Promise.resolve()
unwraps its argument
Promise.resolve()
will always unwrap its argument down to its non-thenable value.
For instance, in the snippet below, we passed a real promise object to the first Promise.resolve()
method. However, the Promise.resolve()
method unwrapped the promise object down to its non-thenable value.
Therefore, the resultant promise object resolved to the "I will code sweetly!"
string value.
const myPromise = Promise.resolve(
Promise.resolve(
Promise.resolve("I will code sweetly!")
)
);
console.log(myPromise);
// The invocation above will return:
Promise { <state>: "fulfilled", <value>: "I will code sweetly" }
Here's another example:
// Create a thenable object:
const firstTest = {
then: function (fulfilled, rejected) {
rejected("Just a trial");
}
}
// Create another thenable object:
const secondTest = {
then: function (fulfilled) {
fulfilled(firstTest);
}
}
// Pass in secondTest's thenable object as an argument to the resolve() method:
Promise.resolve(secondTest);
// The invocation above will return:
Promise { <state>: "rejected", <reason>: "Just a trial" }
You can see that in the snippet above, we passed a thenable object as an argument to the Promise.resolve()
method. However, the Promise.resolve()
method unwrapped the thenable object down to its non-thenable value.
Therefore, the resultant promise object resolved to the "Just a trial"
rejected string value.
Let's now talk about Promise.reject()
.
Build your website with Namecheap
Promise.reject(...)
Promise.reject()
returns a new promise object that is rejected based on the reason passed as an argument to the reject()
method.
Keep in mind that Promise.reject()
is a simpler alternative to the new Promise(...)
syntax—especially when you do not need to use the Promise
constructor's function argument.
For instance, here's one way you can reject a promise object to the string value of "Oops😲!"
.
const myPromise = new Promise(function (resolve, reject) {
reject("Oops😲!");
});
console.log(myPromise);
// The invocation above will return:
Promise { <state>: "rejected", <reason>: "Oops😲!" }
A simpler way of writing the snippet above is by using Promise.reject()
like so:
const myPromise = Promise.reject("Oops😲!");
console.log(myPromise);
// The invocation above will return:
Promise { <state>: "rejected", <reason>: "Oops😲!" }
Important stuff to know about Promise.reject()
Keep these two essential pieces of info in mind whenever you choose to use Promise.reject()
.
Info 1: Promise.reject()
's argument determines the rejected reason
A promise object's rejected reason depends on the data type you passed as an argument to the reject()
method.
For instance, the promise object below is rejected based on the reason of an array because we passed in an array data type as an argument to the reject()
method.
const myPromise = Promise.reject(["Oops😲!"]);
console.log(myPromise);
// The invocation above will return:
Promise { <state>: "rejected", <reason>: Array [ "Oops😲!" ] }
Info 2: Promise.reject()
does not unwrap its argument
Unlike the resolve()
method, Promise.reject()
will not unwrap its argument down to its non-thenable value.
Therefore, suppose you pass in a thenable value as an argument to the reject()
method. In that case, Promise.reject()
will use the thenable value as the rejected state's reason—without unwrapping it.
Here's an example:
const myPromise = Promise.reject({
then: function () {
return "Oops😲!";
},
});
console.log(myPromise);
// The invocation above will return:
Promise { <state>: "rejected", <reason>: Object { then: then() } }
You can see that in the snippet above, we passed a thenable object as an argument to the Promise.reject()
method. However, the Promise.reject()
method did not unwrap the thenable object down to its non-thenable value.
Therefore, the resultant promise object resolved to the rejected reason of the thenable object.
Let's now talk about Promise.all()
.
Promise.all([...])
Promise.all([...])
returns a new promise object that resolves to an array of all()
's iterable argument's values.
Promise.all([...])
will return a fulfilled promise if and when JavaScript fulfills all its iterable argument's values.
However, suppose JavaScript rejects any of the iterable's values. In that case, Promise.all([...])
will return a new promise object that is rejected based on the reason of the first iterable object's value that got rejected.
- The
all()
method's iterable argument's items can be promises, thenables, or any of JavaScript's data types. Promise.all()
will automatically pass each of its iterable argument's items throughPromise.resolve()
. Therefore,all()
will eventually resolve its iterable argument's values to a fully-fledged promise object.
Here's an example:
Promise.all(["Orange", Promise.resolve(24), 715]);
// The invocation above will return:
Promise { <state>: "fulfilled", <value>: Array(3) ["Orange", 24, 715] }
You can see that the snippet above returned a new promise object that fulfilled to an array of all()
's array argument—because all the array's values got fulfilled.
Here's another example:
const item1 = Promise.resolve(24);
const item2 = 715;
const item3 = new Promise((resolve, reject) => {
setTimeout(() => {
resolve("Orange");
}, 30000);
});
Promise.all([item1, item2, item3]).then((val) => {
console.log(val);
});
// The invocation above will first return:
Promise { <state>: "pending" }
// Then after 30000 milliseconds (30 seconds) invocation will return:
Array(3) [24, 715, "Orange"]
The snippet above returned a new promise object that fulfilled to an array of all()
's array argument—because all the array's values got fulfilled.
Now, consider this third example:
Promise.all(["Orange", Promise.reject("Pinkmelon"), Promise.reject(24), 715]);
// The invocation above will return:
Promise { <state>: "rejected", <reason>: "Pinkmelon" }
Notice that the snippet above returned a new promise object rejected based on the reason of the first rejected array item.
But suppose you need your promise object to return after all the iterable's items get resolved—regardless of whether the values settled to a fulfilled or rejected state. In that case, use the Promise.allSettled()
method. Let's talk about it below.
Design and develop at the same time
Promise.allSettled([...])
Promise.allSettled([...])
returns a new promise object that resolves after all allSettled()
's iterable argument's items have either been fulfilled or rejected.
- The
allSettled()
method's iterable argument's items can be promises, thenables, or any of JavaScript's data types. Promise.allSettled()
will automatically pass each of its iterable argument's items throughPromise.resolve()
. Therefore,allSettled()
will eventually resolve all its iterable argument's items to a fully-fledged promise object.
Here's an example:
Promise.allSettled(["Orange", Promise.resolve(24), 715]);
// The invocation above will return:
Promise { <state>: "fulfilled", <value>: Array(3) [
{ status: "fulfilled", value: "Orange" },
{ status: "fulfilled", value: 24 },
{ status: "fulfilled", value: 715 }
]}
You can see that the snippet above returned a new promise object that resolved after all the allSettled()
's array argument's items got fulfilled.
Here's another example:
const item1 = Promise.resolve(24);
const item2 = 715;
const item3 = new Promise((resolve, reject) => {
setTimeout(() => {
resolve("Orange");
}, 30000);
});
Promise.allSettled([item1, item2, item3]).then((val) => {
console.log(val);
});
// The invocation above will first return:
Promise { <state>: "pending" }
// Then after 30000 milliseconds (30 seconds) invocation will return:
Array(3) [
{ status: "fulfilled", value: 24 },
{ status: "fulfilled", value: 715 }
{ status: "fulfilled", value: "Orange" },
]
You can see that the snippet above returned a new promise object that resolved after all the allSettled()
's array argument's items got fulfilled.
Now, consider this third example:
Promise.allSettled(["Orange", Promise.reject("Pinkmelon"), Promise.reject(24), 715]);
// The invocation above will return:
Promise { <state>: "fulfilled", <value>: Array(4) [
{ status: "fulfilled", value: "Orange" },
{ status: "rejected", reason: "Pinkmelon" },
{ status: "rejected", reason: 24 }
{ status: "fulfilled", value: 715 }
]}
Notice that the snippet above returned a new promise object that resolved after all the allSettled()
's array argument's items got either fulfilled or rejected.
But what if you only need your promise object to return with only the item that first resolves to a fulfilled state? In that case, you would use the Promise.any()
method. Let's talk about it below.
Promise.any([...])
Promise.any([...])
returns a new promise object that resolves with any()
's iterable argument's value that first settles to a fulfilled state.
However, suppose none of the items in the iterable object resolved to a fulfilled state. In that case, the any()
method will return a new promise that is rejected with the reason of an AggregateError
.
- The
any()
method's iterable argument's items can be promises, thenables, or any of JavaScript's data types. Promise.any()
will automatically pass each of its iterable argument's items throughPromise.resolve()
. Therefore,any()
will eventually resolve all its iterable argument's items to a fully-fledged promise object.
Here's an example:
Promise.any(["Orange", Promise.resolve(24), 715]);
// The invocation above will return:
Promise { <state>: "fulfilled", <value>: "Orange"}
You can see that the snippet above returned a new promise object that resolved with any()
's array argument's value that first settled to a fulfilled state.
Here's another example:
const item1 = Promise.reject(24);
const item2 = Promise.reject(715);
const item3 = new Promise((resolve, reject) => {
setTimeout(() => {
resolve("Orange");
}, 30000);
});
const item4 = "Pinkmelon";
Promise.any([item1, item2, item3, item4]).then((val) => {
console.log(val);
});
// The invocation above will return: "Pinkmelon"
Among the four promise items of the array we passed into the any()
method, notice that only the first fulfilled promise's value got returned.
Now, consider this third example:
Promise.any([Promise.reject("Pinkmelon"), Promise.reject(24)]);
// The invocation above will return:
Promise { <state>: "rejected", <reason>: AggregateError }
Notice that the snippet above returned a new promise object that is rejected based on the reason of an AggregateError
because all the array argument's items resolved to a rejected state.
But what if you only need your promise object to return with only the item that JavaScript first settled—irrespective of whether JavaScript resolved it to a fulfilled or rejected state? In that case, you would use the Promise.race()
method. Let's talk about it below.
Design in Figma, launch in Webflow
Promise.race([...])
Promise.race([...])
returns a new promise object that resolves with race()
's iterable argument's value that first got settled to either a fulfilled or rejected state.
- The
race()
method's iterable argument's items can be promises, thenables, or any of JavaScript's data types. Promise.race()
will automatically pass each of its iterable argument's items throughPromise.resolve()
. Therefore,race()
will eventually resolve all its iterable argument's items to a fully-fledged promise object.
Here's an example:
Promise.race(["Orange", Promise.resolve(24), 715]);
// The invocation above will return:
Promise { <state>: "fulfilled", <value>: "Orange"}
You can see that the snippet above returned a new promise object that resolved with race()
's array argument's value that first got settled.
Here's another example:
const item1 = Promise.reject(24);
const item2 = Promise.reject(715);
const item3 = new Promise((resolve, reject) => {
setTimeout(() => {
resolve("Orange");
}, 30000);
});
const item4 = "Pinkmelon";
Promise.race([item1, item2, item3, item4]).then((val) => {
console.log(val);
});
// The invocation above will return:
Promise { <state>: "rejected", <reason>: 24 }
You can see that the snippet above returned a new promise object that resolved with race()
's array argument's value that first got settled.
Now, consider this third example:
{
const item1 = new Promise((resolve, reject) => {
setTimeout(resolve, 8000, "Orange");
});
const item2 = new Promise((resolve, reject) => {
setTimeout(reject, 3000, "Banana");
});
const item3 = new Promise((resolve, reject) => {
setTimeout(resolve, 50000, "Grape");
});
Promise.race([item1, item2, item3]).then((val) => {
console.log(val);
});
}
// The invocation above will first return:
Promise { <state>: "pending" }
// Then, after 3000 milliseconds (3 seconds), the invocation will return:
// "Uncaught (in promise) Banana"
Notice that the snippet above returned the value of the promise object that JavaScript first settled.
Keep in mind that race()
will remain pending—forever—if you pass an empty array argument into it.
For instance, consider this code:
Promise.race([]);
// The invocation above will return:
Promise { <state>: "pending" }
The snippet above will never get settled (fulfilled or rejected) because we used an empty array as race()
's argument.
So, now that we know how to use the Promise
constructor's static methods to extend Promise
's functionality, we can talk about an easier way to write promises.
Async and Await: An Easier Way to Implement Asynchronous Programming
The async
and await
keywords provide us with an alternate—and easier—way to write and read promises.
Let's see how the two keywords work.
async
keyword
When you place the async
keyword before a regular function declaration, the keyword will turn that function into an asynchronous function.
By default, an async
function returns a promise object that is either resolved with the function's returned value or rejected based on the exception thrown.
In other words, the async
keyword will automatically pass an asynchronous function's return value through the Promise.resolve()
static method.
A slick computer trick that makes mistakes disappear
Examples
The function below is a regular function that will return a regular string value.
function myMomsPromise() {
return "I get a book";
}
// Invoke the myMomsPromise() asynchronous function:
console.log(myMomsPromise());
// The invocation above will return: "I get a book"
However, placing the async
keyword before the function declaration will turn the function into an asynchronous function.
Therefore, the function will return a promise object instead of a string.
async function myMomsPromise() {
return 'I get a book';
}
// Invoke the myMomsPromise() asynchronous function:
console.log(myMomsPromise());
// The invocation above will return:
Promise { <state>: "fulfilled", <value>: "I get a book" }
The async
keyword instructed the function to return a promise object rather than outputting the string value directly.
The async
function above is similar to writing:
function myMomsPromise() {
return Promise.resolve('I get a book');
}
// Invoke the myMomsPromise() asynchronous function:
console.log(myMomsPromise());
// The invocation above will return:
Promise { <state>: "fulfilled", <value>: "I get a book" }
You can use the async
keyword with function expressions and arrow functions.
// Example of an async function expression:
const myMomsPromise = async function () {
return "I get a book";
};
// Example of an async arrow function expression:
const myMomsPromise = async () => "I get a book";
Now, take a look at the Promise
constructor equivalence of the async function:
const myMomsPromise = new Promise(function (resolve, reject) {
resolve("I get a book");
});
// Check myMomsPromise's content:
console.log(myMomsPromise);
// The invocation above will return:
Promise { <state>: "fulfilled", <value>: "I get a book" }
Keep in mind that an async function is simply a syntactic sugar that makes reading and writing promises easier.
In other words, an async function is a simplified way of writing promises. Therefore, you can still use it to create a promise chain.
Here's an example:
async function myMomsPromise() {
return "I get a book";
}
// Promise chain:
myMomsPromise()
.then((value) => value + " " + "and a car")
.then((value) => console.log(value))
.catch((e) => console.error(e));
// The invocation above will return:
("I get a book and a car");
So, now that we know what an async
keyword does, let's discuss the await
keyword.
Turn your passion into a business
await
keyword
The await
keyword instructs a function to wait for a promise to be settled before continuing its execution.
- The
await
keyword works only inside an async function in a regular JavaScript code. However, in a JavaScript module, you can useawait
on its own—that is, outside an async function. - You can use zero or more
await
expressions in an async function.
Examples
The await
keyword in the snippet below instructs showMomsPromise
to wait for myMomsPromise
to be settled before continuing its execution.
async function showMomsPromise() {
const myMomsPromise = new Promise(function (resolve, reject) {
setTimeout(resolve, 5000, "I get a book");
});
console.log(await myMomsPromise);
}
// Invoke the showMomsPromise() asynchronous function:
showMomsPromise();
// The invocation above will first return:
Promise { <state>: "pending" }
// Then, after 5000 milliseconds (5 seconds), the invocation will return:
"I get a book"
Suppose we had omitted the await
keyword in the snippet above. In such a case, the console.log()
statement would have invoked myMomsPromise
immediately—without waiting for its promise to be resolved.
Consequently, showMomsPromise()
's invocation would have returned a fulfilled promise that is resolved with an undefined
value.
Here's an example:
In the snippet below, JavaScript invoked the console.log(myMomsPromise)
statement immediately because it has no await
keyword.
async function showMomsPromise() {
const myMomsPromise = new Promise(function (resolve, reject) {
setTimeout(resolve, 5000, 'I get a book');
});
console.log(myMomsPromise);
}
// Invoke the showMomsPromise() asynchronous function:
showMomsPromise();
// The invocation above will first return:
Promise { <state>: "pending" }
// Immediately afterwards, the invocation will return:
Promise { <state>: "fulfilled", <value>: undefined }
An async function will always return a promise—even if you do not return any value.
async function showMomsPromise() {}
// Invoke the showMomsPromise() asynchronous function:
showMomsPromise();
// The invocation above will return:
Promise { <state>: "fulfilled", <value>: undefined }
You can see that the async function above returned a promise that resolved with an undefined
value because we did not explicitly tell it to return any item.
Here's another example:
async function showMomsPromise() {
const myMomsPromise = await "I get a book";
console.log(myMomsPromise);
}
// Invoke the showMomsPromise() asynchronous function:
showMomsPromise();
// The invocation above will first return:
"I get a book"
// Then, it will also return:
Promise { <state>: "fulfilled", <value>: undefined }
The async function in the snippet above returned a promise with an undefined
value because we did not tell it to return any value. Instead, we only told it to log "I get a book"
to the browser's console.
Here's a third example:
async function showMomsPromise() {
const myMomsPromise = await "I get a book";
console.log(myMomsPromise);
return myMomsPromise;
}
// Invoke the showMomsPromise() asynchronous function:
showMomsPromise();
// The invocation above will first return:
"I get a book"
// Then, it will also return:
Promise { <state>: "fulfilled", <value>: "I get a book" }
You can now see that the async function above returned a promise object that fulfilled with the function's output.
So, now that you know how the async
and await
keywords work, let's convert a then()
method snippet we saw previously into an async/await code.
Build your website with Namecheap
How to Convert a then()
Promise Code to an Async/Await Program
Remember that we used the snippet below previously to represent mom's promise and its eventual fulfillment (or rejection).
Take some time to review the code.
// Create two variables:
let finalResponse = null;
const passedExam = true;
// Create a function that return mom's first promise:
function emitFirstProm() {
return "I get a book";
}
// Create a function that add mom's first promise to her second one:
function emitSecondProm(firstPromise) {
return firstPromise + ", a phone";
}
// Create a function that add mom's previous promises to her third one:
function emitThirdProm(pastProms) {
return pastProms + ", a laptop";
}
// Create a function that add mom's previous promises to her fourth one:
function emitFourthProm(pastProms) {
return pastProms + ", and a Jeep 🤩🎉✨";
}
// Create mom's promise:
const myMomsPromise = new Promise(function (resolve, reject) {
if (passedExam) {
resolve(emitFirstProm());
} else {
reject("You did not pass your exam, so mom rejected her promise.");
}
});
// Invoke each of the functions above asynchronously by attaching them to myMomsPromise object:
myMomsPromise
.then(emitSecondProm)
.then(emitThirdProm)
.then(emitFourthProm)
.then(function (value) {
finalResponse = `Mom fulfilled her promise! ${value}`;
})
.catch(function (e) {
console.error(`Oops! Caught the following error: ${e}`);
})
.finally(function () {
console.log(finalResponse);
});
// The invocation above will return:
// "Mom fulfilled her promise! I get a book, a phone, a laptop, and a Jeep 🤩🎉✨"
The snippet above used a chain of then()
methods to attach callbacks to the myMomsPromise
object.
We can make the snippet much simpler and easier to read by replacing the then()
blocks with async/await like so:
// Create two variables:
let finalResponse = null;
const passedExam = true;
// Create a function that return mum's first promise:
function emitFirstProm() {
return "I get a book";
}
// Create a function that add mum's first promise to her second one:
function emitSecondProm(firstPromise) {
return firstPromise + ", a phone";
}
// Create a function that add mum's previous promises to her third one:
function emitThirdProm(pastProms) {
return pastProms + ", a laptop";
}
// Create a function that add mum's previous promises to her fourth one:
function emitFourthProm(pastProms) {
return pastProms + ", and a Jeep 🤩🎉✨";
}
// Create mom's promise:
const myMomsPromise = async function () {
if (passedExam) {
const firstPromise = await emitFirstProm();
const secondPromise = await emitSecondProm(firstPromise);
const thirdPromise = await emitThirdProm(secondPromise);
const fourthPromise = await emitFourthProm(thirdPromise);
finalResponse = `Mom fulfilled her promise! ${fourthPromise}`;
} else {
throw new Error("You did not pass your exam, so mom rejected her promise.");
}
};
// Invoke the myMomsPromise async function:
myMomsPromise()
.catch(function (e) {
console.error(`Oops! Caught the following error: ${e}`);
})
.finally(function (e) {
console.log(finalResponse);
});
// The invocation above will return:
// "Mom fulfilled her promise! I get a book, a phone, a laptop, and a Jeep 🤩🎉✨"
You can see in the snippet above that we no longer need a chain of then()
blocks. Instead, we used the await
keyword before each function call.
Then, we assigned the returned promise objects to the firstPromise
, secondPromise
, thirdPromise
, and fourthPromise
variables, respectively.
Each await
keyword instructs myMomsPromise
to pause its execution until the keyword's function call returns its result. Therefore, all subsequent code after the paused line will not execute until await
's function call returns its value.
You can also use a hybrid of the then()
block, and the async/await keywords to make your code more flexible.
Here's an example:
// Create two variables:
let finalResponse = null;
const passedExam = true;
// Create a function that return mum's first promise:
const emitFirstProm = () => "I get a book";
// Create a function that add mum's first promise to her second one:
const emitSecondProm = (firstPromise) => firstPromise + ", a phone";
// Create a function that add mum's previous promises to her third one:
const emitThirdProm = (pastProms) => pastProms + ", a laptop";
// Create a function that add mum's previous promises to her fourth one:
const emitFourthProm = (pastProms) => pastProms + ", and a Jeep 🤩🎉✨";
// Create mom's promise:
const myMomsPromise = async function () {
if (passedExam) {
const firstPromise = await emitFirstProm();
const secondPromise = await emitSecondProm(firstPromise);
const thirdPromise = await emitThirdProm(secondPromise);
const fourthPromise = await emitFourthProm(thirdPromise);
return `Mom fulfilled her promise! ${fourthPromise}`;
} else {
throw new Error("You did not pass your exam, so mom rejected her promise.");
}
};
// Invoke the myMomsPromise async function:
myMomsPromise()
.then((value) => (finalResponse = value))
.catch((e) => console.error(`Oops! Caught the following error: ${e}`))
.finally(() => console.log(finalResponse));
// The invocation above will return:
// "Mom fulfilled her promise! I get a book, a phone, a laptop, and a Jeep 🤩🎉✨"
Before we wrap up our discussion on asynchronous programming, you should be aware of how an async function's execution order works. So, let's talk about that below.
Learn Flexbox with Images
How Does an Async Function's Execution Order Work?
Consider the following MDN's statements:
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's body, however, the async function will always complete asynchronously.
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.— MDN
Let's use an example to understand the statements above.
Example of How an Async Function's Execution Order Works
Consider this snippet below:
function myMomsPromise() {
console.log("Starting mom's promise check...");
return new Promise((resolve) => {
setTimeout(function () {
resolve("I get a book.");
console.log("Mom's promise is fulfilled!");
}, 5000);
});
}
function myDadsPromise() {
console.log("Starting dad's promise check...");
return new Promise((resolve) => {
setTimeout(function () {
resolve("I get a pen.");
console.log("Dad's promise is fulfilled!");
}, 1000);
});
}
async function checkMomAndDadsPromises() {
console.log("===Promises confirmation check started===");
const momsPromiseResponse = await myMomsPromise();
console.log(momsPromiseResponse);
const dadsPromiseResponse = await myDadsPromise();
console.log(dadsPromiseResponse);
}
function startPromiseChecks() {
checkMomAndDadsPromises();
console.log("I prayed while waiting for mom's promise.");
console.log("Mom's promise is worth waiting for.");
setTimeout(() => console.log("Is mom's promise still pending?"), 5100);
}
startPromiseChecks();
Here's what we did in the snippet above:
- We use a regular function (
startPromiseChecks
) to run an async function (checkMomAndDadsPromises
) - We programmed the
checkMomAndDadsPromises
async function to await two promises (myMomsPromise
andmyDadsPromise
) successively.
Now, when startPromiseChecks()
gets invoked, in what order do you think each of startPromiseChecks
' code would execute? Let's find out.
startPromiseChecks
will first invoke thecheckMomAndDadsPromises
async function.checkMomAndDadsPromises
will start its execution synchronously until it reaches its firstawait
expression's pending promise.- On getting to its first
await
expression,checkMomAndDadsPromises
will:- Log
"===Promises confirmation check started==="
and"Starting mom's promise check..."
to the browser's console. - Wait for
myMomsPromise
's resolution—while also yielding control back tostartPromiseChecks
(the function that invokedcheckMomAndDadsPromises
).
- Log
- While awaiting
myMomsPromise
's settlement,startPromiseChecks
—which now has the control—will continue executing its code. - After a while (
5000
milliseconds), whenmyMomsPromise
gets settled,checkMomAndDadsPromises
will regain control. Therefore, it will:- Assign the first
await
expression's resolution tomomsPromiseResponse
. - Log the
momsPromiseResponse
's value to the browser's console. - Evaluates its second
await
expression - Log
"Starting dad's promise check..."
to the browser's console. - Wait for
myDadsPromise
's resolution—while also yielding control back tostartPromiseChecks
(the function that invokedcheckMomAndDadsPromises
).
- Assign the first
- While awaiting
myDadsPromise
's settlement,startPromiseChecks
—which now has the control—will continue executing its code. - After a while (
1000
milliseconds), whenmyDadsPromise
gets settled,checkMomAndDadsPromises
will regain control. Therefore, it will:- Assign the second
await
expression's resolution todadsPromiseResponse
. - Log the
dadsPromiseResponse
's value to the browser's console.
- Assign the second
After logging dadsPromiseResponse
's value to the browser's console, if any return
expression exists inside the checkMomAndDadsPromises
function, the computer will yield control to the expression.
However, none exist in our case, so startPromiseChecks()
's invocation output will look like so:
===Promises confirmation check started===
Starting mom's promise check...
I prayed while waiting for mom's promise.
Mom's promise is worth waiting for.
undefined
Mom's promise is fulfilled!
I get a book.
Starting dad's promise check...
Is mom's promise still pending?
Dad's promise is fulfilled!
I get a pen.
Overview
This article discussed how asynchronous programming works in JavaScript. We also learned how to implement async programming with callbacks and promises.
And lastly, we saw how async
and await
keywords make it easier to write promises.