How Promises and Await work in Javascript
📣 Sponsor
Javascript is an asynchronous language. That means that a line of code can run, and fire off an action, while the next line runs. If we need to wait for the action to complete, that becomes a problem.
A perfect example of this is running an API. Suppose we use fetch to get data from an API, and then need to use it on the next line:
const getPage = fetch('https://fjolt.com/api/importantData');
console.log(getPage); // Returns Promise {pending} or undefined
const useData = getPage.json().showMeTheMoney; // Returns a type error.
So we tried to fetch a page, and then use data which that page should return. The problem is that the fetch request was still running, and the other lines completed their execution. fetch
runs asynchronously, which means it fires off, and the other lines can go on while it tries to get its answer.
We have to somehow wait for fetch to complete, to get the response we need.
Await
Await
is the keyword we use when we want to wait for a line to finish, but it only works in certain situations:
- In an
async
function - When the line returns a promise
In the future, we will be able to use await outside of async
functions, but you typically need one these days. To understand await then, we need to understand promises.
Promises
We are familiar with the concept that a function returns something. A promise is a type of return which will return a value when we call resolve
or reject
an outcome. To get our head around that, let's look at a typical promise function.
function myPromise() {
return new Promise(resolve => {
setTimeout(function() {
resolve('Hey there');
}, 4000);
});
}
async function runMyPromise() {
console.log(await myPromise()); // returns: Hey there
}
resolve
denotes the return value for this particular promise. As you can see, despite the fact that we wait 4 seconds before saying 'Hey there' in our myPromise()
function, when we 'await
' for the promise to return a value, we can still console log its final value.
To answer the thought on your mind yes, this does stop the code for 4 seconds - so be careful when using promises. An API call which stops rendering of a web page because of await
may result in much longer page load times.
Rejections
Promises can be resolved or rejected. In the above example, we only resolved the function - but we can use reject to give us some error handling capabilities.
Rejections are best used with the then().catch()
notation.
Then/Catch
Functions which return a promise are said to be thennable. That means they can have then attached to the end of them. Anything within then will fire whenever the promise is resolved. Similarly, catch()
will return any rejections from the promise. A benefit of this format is that we don't have to use an async
function.
Let's take a look at an example:
function myPromise() {
return new Promise((resolve, reject) => {
setTimeout(function() {
reject('Too Soon!')
}, 2000);
setTimeout(function() {
resolve('Hey there');
}, 4000);
});
}
myPromise().then((data) => {
console.log(`Success: ${data}`);
}).catch((data) => {
console.log(`Error: ${data}`);
});
// The above returns: Error: Too Soon!
Since after 2 seconds, we fire a reject
line on our promise, the then/catch clause rejects the promise, and returns an error, which is passed into the catch statement. This lets us do error handling in a function, if we need to.
Thennable functions are also chainable, with the return value of the last then passed to the next one - so we could write something like this:
function addTwo(x) {
return new Promise(resolve => {
setTimeout(function() {
resolve(x + 2);
}, 1000);
})
}
async function myFunction() {
const Addition = await addTwo(2).then(data => data + 4).then(data => data + 10).then(data => data - 4);
console.log(Addition) // returns: 14
}
In the example above, I want to console log the value of a promise based variable. If I was doing everything else within then()
, then that would be no problem, but since my console log is outside of the then
clause we need to await
on that line to finish.
Since then returns a promise, we can easily await for all of the additions to be completed before doing any console logs.
Real Life Example: fetch()
Timeouts are fun, but what is the real life application of this kind of thing? One of the most useful examples is fetch()
, a function which gets the response of any HTTP request. Fetch returns a promise, so we can easily wait for the data to come back from the request, before proceeding to the next line of code:
async function getFjolt() {
const page = await fetch('https://fjolt.com/', { method: 'GET' });
return page;
}
getFjolt().then((data) => {
console.log(data);
})
The above example waits for the response from the webpage before console logging the outputs. That means we can wait for the response from another server, before continuing in a particular function.
More Tips and Tricks for Javascript
- Types may be coming to Javascript
- Javascript Array Filter Method
- Javascript Arrays
- Javascript Objects Cheatsheet
- Creating a NodeJS Push Notification System with Service Workers
- Javascript Functions
- Updating Object Key Values with Javascript
- How to install nvm, the Node Version Manager
- Javascript Comments
- What are the three dots (...) or spread operator in Javascript?