“Are you ready?” Klaus asked finally.
Lemony Snicket, The Ersatz Elevator
“No,” Sunny answered.
“Me neither,” Violet said, “but if we wait until we’re ready we’ll be waiting for the rest of our lives, Let’s go.”
Introduction
Remember those times you are in a queue, where you must wait for quite a while for getting some you want. You could be waiting for your turn for a 5-minute ride in Universal Studios, entry for an exhibition, or simply buying a currently hyped drink. What did you do then? If you are like the most of us, either you would be checking or playing with your mobile phone, talking to a friend or family member you were with, or probably eating snacks.
In short, we can say that you are doing something else while waiting for something to finish.
Now, apply the waiting scenario above to programming terms. You are the program. Waiting in a queue will be something we call loading, where every time you move forward, the percentage gets closer to 100%. Of course, you would be doing something else in a queue: playing, eating, talking, or whatever. So, as a program you can do another task until the wait is over.
There are two ways to create an asynchronous function in JavaScript. The first is promise
, the second is async-await
. Let’s get started.
Promises
This snippet below shows an example on how to create a promise in JavaScript:
const promise = new Promise((resolve, reject) => {
setTimeout(() => {
const result = doSomething();
if (result) {
resolve(result);
}
reject('No results found.');
}, 1000);
});
We simply create a new promise, with an argument of a callback function requiring resolve
and reject
parameters. A setTimeout()
is called to call doSomething()
after 1000 milliseconds. Then we call doSomething()
which requires some time to return the result. Once result
is returned, we do a simple checking if result
has a value. If it does, return the result by calling resolve()
. If it does not have a value, return the result as string by calling reject()
.
And this is how to call a promise:
promise
.then((res) => {
console.log(res);
})
.catch((err) => {
console.log("error", err);
})
.finally(() => {
console.log("No matter what, it's finished");
});
As you can see, we have a then()
, catch()
, and finally()
. When the promise resolves, the callback in a then()
will be called. And when the promise rejects, it will be caught in catch()
. Lastly, finally()
will always be called whether the promise resolves or rejects.
So, how do we know using promise is asynchronous? See this snippet below:
const doSomething = () => {
return "Something";
};
const promise = new Promise((resolve, reject) => {
setTimeout(() => {
const result = doSomething();
if (result) {
resolve(result);
}
reject('No results found.');
}, 1000);
});
console.log("Before promise");
promise.then((res) => {
console.log(res);
})
.catch((err) => {
console.log("error", err);
})
.finally(() => {
console.log("No matter what, it's finished");
});
console.log("After promise");
// The results:
/*
"Before promise"
"After promise"
"Something"
"No matter what, it's finished"
*/
Do you notice that the last text is printed from the callback of finally()
, not “After promise”? An asynchronous function runs in background, without interrupting the main function. It’s like ordering someone to do something while we continue our work. Which is why “After promise” is printed after “Before promise” directly, not after “No matter what, it’s finished”.
And then, after 1000 milliseconds, the promise resolves, prints “Something” from the callback of then()
, then it calls the callback of finally()
, which prints “No matter what, it’s finished”.
You can also chain multiple promises as shown in this post.
Sometimes we would want to wait for the promise function to finish first to get the result, because without the data from the promise, we cannot produce the correct output. So, let’s learn async-await.
Async-Await
Actually, resolving or rejecting promise by using then()
is totally possible, by using the technique of nested promises, or promise chaining. But in ES6, JavaScript introduces the async-await to make it easier for us programmers to read.
Here is a snippet of async-await, using the same logic as the promise example above.
const doSomething = () => {
return "Something";
};
const promise = async () => {
return new Promise((resolve, reject) => {
setTimeout(() => {
const result = doSomething();
if (result) {
resolve(result);
}
reject('No results found.');
}, 1000);
});
};
const callPromise = async () => {
console.log("Before promise");
try {
const result = await promise();
console.log(result);
}
catch (err) {
console.log(err);
};
console.log("After promise");
};
callPromise();
// The results:
/*
"Before promise"
"Something"
"After promise"
*/
First, we create a new async function, to wrap the new Promise()
part of code and name it promise()
. Second, we create another async function called callPromise()
. Inside callPromise()
, we call the promise()
function with an await. Then we print the result. If the promise rejects, it will be thrown and caught inside the catch
.
Await can only be used on an async function, and can only be called from an async function. It waits until the promise resolves or rejects and then continue to the next statements inside the async function. Calling await outside of an async function will return an error.
Calling an async function without await is possible, the result is like calling promise.then()
, as shown from the example from the Promise section before. It will not wait for the result to be resolved or rejected because the result is a promise object. To get the result, use then()
, or add await on the result. See the snippet below:
const func = async () => {
return new Promise((resolve, reject) => {
setTimeout(() => { resolve(2); }, 1000);
})
};
const func2 = async() => {
const result = func(); // result is a promise object.
console.log(await result); // 2
}
func2();
Conclusion
Asynchronous function is a very important concept to learn if you are working with APIs and other external resources. Personally, I prefer using async-await. Because async-await makes the code cleaner and easier to read. Multiple-nested promises can be pretty nasty if you have to trace a code for the first time. But, it’s important to learn both to understand the concept first.
I hope it helps you! Live your code and code your life!
“Multi-tasking is great in the kitchen when you are trying to time the chicken to be ready at the same time as the potatoes. But do not assume it is a great way to manage a workday.”
― Joanne Tombrakos, It Takes An Egg Timer, A Guide to Creating the Time for Your Life