DEV Community

Reuben George Thomas
Reuben George Thomas

Posted on

Promises, Promise.all and async/await explained in 5 minutes

First, a quick intro to promises

A promise is an special kind of object expecting a value at some future point.

It can be thought of as a placeholder for the result returned by an asynchronous process.

I really like MDN's wording on why these are called promises:

Promises let asynchronous methods return values like synchronous methods: instead of immediately returning the final value, the asynchronous method returns a promise to supply the value at some point in the future.


let iPromiseToGiveYouTheValue = new Promise((resolve,reject)=>{
  //dostuffthattakestime
  if(stuffworked)
  resolve("yay it worked, here's a cookie");
  else 
  reject(new Error("oh no it didn't work"));
}
Enter fullscreen mode Exit fullscreen mode

The promise object returned by

new Promise

has three states:

  • initially "pending"
  • calling resolve() sets the state to "resolved"
  • calling reject() sets the state to "rejected"

The important thing to note here is that once the promise object has been set up, any code after the promise declaration will run, and the promise will resolve or reject at a later stage.

Most of the time you won't be creating a promise using new Promise() , you'll be using functions that return promises.. like the browser's fetch() api.


So how do I access the value from a promise?


.then()

Think of this like an event listener for a "resolve" event

 myPromise.then((resolvedValue)=>{
   ///dosomethingwithresult
 })
Enter fullscreen mode Exit fullscreen mode

When the promise resolves successfully, .then() will execute the first callback function provided to it.


There is an optional second argument to .then(), which is the function to call when the promise is rejected.

myPromise.then(
  (resolvedValue)=>{console.log(resultFromPromise)},
  (errorFromPromise)=>{console.log(errorFromPromise)}
)
Enter fullscreen mode Exit fullscreen mode

.then() will automatically pass in the resolved value from the promise, as the first argument to its callback function.

Which means you can do something like this:

 myPromise.then(console.log)
Enter fullscreen mode Exit fullscreen mode

The result from the promise gets passed in to console.log(), so the above code is equivalent to:

 myPromise.then((resolvedValue)=>{
   console.log(resolvedValue)
 })
Enter fullscreen mode Exit fullscreen mode

.catch()

Does the same thing as providing a second callback function to .then().
It handles the rejected value of the promise.

 .catch((errorFromPromise)=>{throw errorFromPromise})
Enter fullscreen mode Exit fullscreen mode

.finally()

Runs its callback function for BOTH resolved and rejected states.

Useful for doing cleanup, for example if the promise is an api request, stopping any loading animations when it has completed(whether rejected or resolved).


 myPromise
  .then((resultFromPromise)=>{
     console.log(resultFromPromise)
   })
  .finally(()=>{
     //do stuff regardless of resolved or rejected state
   })
  .catch((errorFromPromise)=>{
     throw errorFromPromises
   })
Enter fullscreen mode Exit fullscreen mode

Note that the callback for .finally() doesn't take any arguments.

Both resolved and error values will pass through a . finally() to the next .then() or .catch()!


Promise.all()

This is useful when you want to run multiple promises at once, and wait for all the values to come back before processing them.

eg: if you have simultaneous api calls, and have some code that relies on all of them completing.


The syntax is:

let bigBadPromise = Promise.all([...babyPromises])
Enter fullscreen mode Exit fullscreen mode

Promise.all() takes in an array of promises as an argument.

bigBadPromise will wait for all of the individual promises to resolve before resolving itself.

It will produce an array of resolved/rejected promises as its result.


If any of the individual promises are rejected, bigBadPromise will immediately reject with that specific error.

It preserves the order of babyPromises, so the order of results in the result array is predictable.


Promise.all example

A fictional api "api.foundersandcoders.com" with two endpoints returning the names of members of two cohorts of students (fac17 and fac18 respectively)

const fac17Promise=
fetch("https://api.foundersandcoders.com/fac17");
const fac18Promise=
fetch("https://api.foundersandcoders.com/fac18");

Promise.all([fac17Promise,fac18Promise])
  .then((students)=>{
    console.log(students);
    //will output [fac17MembersArray, fac18MembersArray]
  })
  .catch((err)=>{
    throw err;
  });
Enter fullscreen mode Exit fullscreen mode

Async/Await


Put "async" in front of a function

let students = async () => {

}
Enter fullscreen mode Exit fullscreen mode

And we can use "await" to wait for promises that take their time in getting back to us.

You can only use await INSIDE AN ASYNC FUNCTION.


An example

let getFac17Students = async () => {
  const students = await fetch("https://api.foundersandcoders.com/fac17");
 console.log(students)
}
Enter fullscreen mode Exit fullscreen mode

Without async await, line 3 would console.log 'undefined', as the fetch request would still be processing.

Await blocks the function from running further code, until the fetch request has resolved.


But what does the 'async' keyword do to my functions?!!!

It automatically converts them into functions that return a promise

The return value will be wrapped inside the promise as it's resolve/reject value.

This allows us to convert any task to return a promise, using 'async', and then use await to wait for it to resolve.


An example is if you have multiple functions that do asynchronous tasks, and a need to pipe the output from one as the input for the next one.

That means you have to block these asynchronous processes individually and wait for their results one by one before starting the next process.


const task1 = async () => { //do stuff }
const task2 = async (outputFromtask1) => { //do stuff with outputFromtask1 }
const task3 = async (outputFromtask2) => { //do stuff with outputFromtask2}

const doManyThings= async () => {
  var resultOne = await task1();
  var resultTwo = await task2(resultOne);
  var finalResult = await task3(resultTwo);
  return finalResult;
}
Enter fullscreen mode Exit fullscreen mode

This is cool, we can use await to block asynchronous processes and make them run synchronously.


Pitfalls?

The function doManyThings() has to have the "async" keyword, because to use await, you have to be inside an async function.

This means that to call doManyThings and access finalResult elsewhere, you cant just assign it to a variable


const finalResult = doManyThings()
Enter fullscreen mode Exit fullscreen mode

This would not work, since doManyThings returns a promise.


This is what you'd have to do

let finalResult;
doManyThings()
 .then(result=>{
  finalResult=result
  //do things with finalResult
 })
 .catch(err=>{
 console.log(err)
 })
 //finalResult will be undefined if you try to use it outside the promise chain
 //you can only use finalResult within the .then()
Enter fullscreen mode Exit fullscreen mode

This is a possible downside of async await.. you could end up with having all your code to handle a result , inside a .then()

But compared to the advantages that it offers, this isn't a big deal

As long as you remember that a function with the async keyword ALWAYS RETURNS A PROMISE, you're golden.

Top comments (0)