Javascript

How Promises and Await work in Javascript

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:

javascript Copy
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.

javascript Copy
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:

javascript Copy
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:

javascript Copy
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:

javascript Copy
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.

Last Updated Tuesday, 20 April 2021