Promises and async/await
Let's dive a bit into the world of Asynchronous functions, functions that we call now but finish later. In this section, we will see why we need Promise and async/await.
Let's start with a simple problem as shown in the following code snippet. We are given a task to update an array with a function, after 1
second of calling the function:
let syncarray = ["1", "2", "3", "4", "5"] function addB() { setTimeout(() => { syncarray.forEach((value, index)=>{ syncarray[index] = value + "+B" }) console.log("done running") }, 1000) } addB() console.log(syncarray); // output // ["1", "2", "3", "4", "5"] // "done running"
console.log(syncarray)
is executed before the addB()
function, hence we see the syncarray
output before it is being updated. This is an Asynchronous behavior. One of the ways to solve this is to use a callback:
let syncarray = ["1", "2", "3", "4", "5"] function addB(callback) { setTimeout(() => { syncarray.forEach((value, index)=>{ syncarray[index] = value + "+B" }) callback() //call the callback function here }, 1000) } addB(()=>{ // here we can do anything with the updated syncarray console.log(syncarray); }) // output // [ '1+B', '2+B', '2+B', '4+B', '5+B' ]
Using the preceding callback approach means that we always pass in callbacks in order to perform other operations on the updated syncarray
function. Let's update the code a little, and this time we'll also add the string "A"
to syncarray
and then print out the updated array:
let syncarray = ["1", "2", "3", "4", "5"] function addB(callback) { setTimeout(() => { syncarray.forEach((value, index) => { syncarray[index] = value + "+B" }) callback() //call the callback function here }, 1000) } addB(() => { setTimeout(() => { syncarray.forEach((value, index) => { syncarray[index] = value + "+A"; }) console.log(syncarray); }, 1000) }) // output // [ '1+B+A', '2+B+A', '3+B+A', '4+B+A', '5+B+A' ]
The preceding code block shows a quick way of passing callback
. Based on the arrow function we discussed, it can be more organized by creating a named function.
Cleaning callbacks with promises
Using callbacks quickly becomes unwieldy and can quickly descend into callback hell. One method of freeing ourselves from this is to make use of Promises. Promises makes our callbacks more organized. It gives a chainable mechanism to unify and orchestrate code that is dependent on previous functions, as you'll see in the following code block:
let syncarray = ["1", "2", "3", "4", "5"] function addA(callback) { return new Promise((resolve, reject) => { setTimeout(() => { syncarray.forEach((value, index) => { syncarray[index] = value + "+A"; }) resolve() }, 1000); }) } addA().then(() => console.log(syncarray)); //output //[ '1+A', '2+A', '2+A', '4+A', '5+A' ]
In the preceding code snippet, setTimeout
is wrapped inside the Promise
function. A Promise
is always instantiated using the following expression:
New Promise((resolve, rejection) => { })
A Promise
is either resolved or rejected. When it is resolved, then we are free to do other things, and when it is rejected, we need to handle the error.
For example, let's ensure that the following Promise
is rejected:
let syncarray = ["1", "2", "3", "4", "5"] function addA(callback) { return new Promise((resolve, reject) => { setTimeout(() => { syncarray.forEach((value, index) => { syncarray[index] = value + "+A"; }) let error = true; if (error) { reject("just testing promise rejection") } }, 1000); }) } addA().catch(e => console.log(e)) // just testing promise rejection
And whenever we have multiple promises, we can use the .then()
method to handle each one:
addA.then(doB) .then(doC) .then(doD) .then(doF) .catch(e= > console.log(e));
The use of multiple .then()
methods to handle numerous promises can quickly become unwieldy. To prevent this, we can use methods such as Promise.all()
, Promise.any()
, and Promise.race()
.
The Promise.all()
method takes in an array of promises to be executed, and will only resolve when all promises are fulfilled. In the following code snippet, we add another Asynchronous function to our previous example and use Promise.all()
to handle them:
let syncarray = ["1", "2", "2", "4", "5"] function addA() { return new Promise((resolve, reject) => { setTimeout(() => { syncarray.forEach((value, index) => { syncarray[index] = value + "+A"; }) resolve() }, 1000); }) } function addB() { return new Promise((resolve, reject) => { setTimeout(() => { syncarray.forEach((value, index) => { syncarray[index] = value + "+B"; }) resolve() }, 2000); }) } Promise.all([addA(), addB()]) .then(() => console.log(syncarray)); // [ '1+A+B', '2+A+B', '2+A+B', '4+A+B', '5+A+B' ]
From the output in the preceding section, you can see that each Asynchronous function gets executed in the order it was added, and the final result is the effect of both functions on the syncarray
variable.
The promise.race
method, on the other hand, will return as soon as any promise in the array is resolved or rejected. You can think of this as a race where each promise tries to resolve or reject first, and as soon as this happens, the race is over. To see an in-depth explanation as well as code examples, you can visit the MDN docs here: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Promise/any.
And finally, the promise.any
method will return on the first fulfilled promise irrespective of any other rejected promise
function. If all promises are rejected, then Promise.any
rejects promises by providing errors for all of them. To see an in-depth explanation as well as code examples, you can visit the MDN docs here: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Promise/race.
While using promises to work with callback solves a lot of issues, there is an even better way of implementing or using them. These are called async/await functions. We'll introduce these functions and show you how to use them in the following section.
async/await
As said earlier, async/await provides a more elegant way of working with promises. It gives us the power to control how and when each promise function gets called inside a function, instead of using .then()
and Promise.all()
.
The following code snippet shows how you can use async/await in your code:
Async function anyName() { await anyPromiseFunction() await anyPromiseFunction() }
The preceding async
function can contain as many promise functions as possible, each waiting for the other to execute before being executed. Also, note that an a
sync
function is resolved as a Promise
. that is, you can only obtain the return variable of the preceding anyName
function (or resolve the function) using .then()
or by calling it in another async
/await
function:
Async function someFunction() { await anyPromiseFunction() await anotherPromiseFunction() return "done" } // To get the returned value, we can use .then() anyName().then(value => console.log(value)) // "done" // we can also call the function inside another Async/await function Async function resolveAnyName() { const result = await anyName() console.log(result) } resolveAnyName() // "done"
With this knowledge, here is how we can rewrite the promise execution from the previous section instead of using Promise.all([addA(), addB()])
:
let syncarray = ["1", "2", "2", "4", "5"] function addA(callback) { return new Promise((resolve, reject) => { setTimeout(() => { syncarray.forEach((value, index) => { syncarray[index] = value + "+A"; }) resolve() }, 1000); }) } function addB(callback) { return new Promise((resolve, reject) => { setTimeout(() => { syncarray.forEach((value, index) => { syncarray[index] = value + "+B"; }) resolve() }, 2000); }) } Async function runPromises(){ await addA() await addB() console.log(syncarray); } runPromises() //output: [ '1+A+B', '2+A+B', '2+A+B', '4+A+B', '5+A+B' ]
You can see from the preceding output that we have the same output as when using the Promise.all
syntax, but are adopting a minimal and cleaner approach.
Note
One drawback of using multiple awaits as opposed to promise.all
is efficiency. Though minor, promise.all
is the preferred and recommended way to handle multiple independent promises.
This thread (https://stackoverflow.com/questions/45285129/any-difference-between-await-promise-all-and-multiple-await) on Stack Overflow clearly explains why this is the recommended way to handle multiple promises.
In the next section, we'll discuss object-oriented programming (OOP) in JavaScript, and how to use ES6 classes.