JavaScript is an event driven language. This means that JS will keep executing code while listening to other events.
Ok, I know, this may not sound so clear, so take an example:

function one() {
  console.log(1);
}
  
function two() {
  console.log(2);
}

one();
two();
  
// => 1
// => 2

As you can see, JavaScript executes the functions above in order. But are you sure that it will wait until function one has terminated, before executing function two?

function one() {
  setTimeout(() => console.log(1), 100);
}
  
function two() {
  console.log(2);
}

one();
two();
  
// => 2
// => 1

Wow! Other languages such as PHP, Ruby and Python would have waited until function one had finished before executing the next function, but JavaScript did not.
So, what if you need to make sure that function two will be called in the right order, after the function one?

Callbacks to the rescue!

function one(callback) {
  setTimeout(() => { 
    console.log(1);
    callback(); 
  }, 100);
}
  
function two() {
  console.log(2);
}

one(() => two());
  
// => 1
// => 2

Now take a look at the function above: we added an argument called callback which is a function that will be executed after printing 1 to the console.
That way, we are sure that the function two will be executed after logging 1.
While this may seems a little bit confusing, let’s see another example:

function getUsers(callback) {
  const users = getUsersFromAPI();
  callback(users);
}
  
getUsers((users) => console.log(users));

/*
  [
    {
      name: "Mitch",
      age:  24
    },
    ...
  ]
*/

Let’s analyze the code below:
we declared a function called getUsers which accepts a callback function as argument. It will perform an AJAX request, and when it will be done, will invoke the callback function passing the AJAX response.
We declared a second function called printUsers which accepts a generic argument (it can be a string, integer, an object etc…), and it ill just print that argument to the console.
We will invoke the getUser method passing printUsers as callback so, as you can see, it will be ready to print users to the console once the HTTP Request will have a response.

Great but… what are the disavantages of this approach?

Async/Await

This is called The Pyramid of Doom or Callback Hell.
While using callbacks will ensure that your code will be executed in a certan way, it will be harder to maintain and scale your code.
So here comes the Promise monad.

Using promises

MDN describes Promises in the following way:

The Promise object represents the eventual completion (or failure) of an asynchronous operation, and its resulting value.

Let’s see how it looks:

function myFirstPromise() {
  return new Promise((resolve) => resolve(100));
}
  
myFirstPromise()
  .then((result) => console.log(result));
  // => 100

Wow, we do not longer need to pass a callback to myFirstPromise to execute the code synchronously!
We just need to return a new instance of the Promise object and resolve it once we got our desidered value.
We use then in order to use the resolved value, once it’s ready.

Real world example

A great use case for the Promise monad is the fetch api:

fetch('https://yesno.wtf/api')
  .then((res)  => console.log(res.body))
  .catch((err) => console.error(err));
  
/*
  {
    "answer": "no",
    "forced": false,
    "image": "https://yesno.wtf/assets/no/14-cb78bf7104f848794808d61b9cd83eba.gif"
  }
*/

As you can see, the fetch api returns a Promise by default.
That way, we can call a REST API service and once we got the response back, we can log it to the console.
If the request fails for any reason, we will use catch in order to handle the error.

The problem

Promises are awesome. They helps developer to write more straightforward code which is self-describing, declarative and more maintainable, but they can lead to a big problem:

The scope of a promise-resolved value.

fetch('https://yesno.wtf/api')
  .then((res)  => res.body)
  .catch((err) => err);
  
console.log(res); // => undefined
console.log(err); // => undefined

As you can see, result and error only exists in then and catch methods.
So what if we need to get multiple Promise results?
Let’s say we want to call the yesno.wtf/api REST API 5 times and put them inside an array.

We could do so:

const arr = [];

function callApi() {
  fetch('https://yesno.wtf/api')
    .then((res)  => arr.push(res.body.answer))
    .catch((err) => err);
}
  
callApi();
callApi();
callApi();
callApi();
callApi();

console.log(arr);
setTimeout(() => console.log(arr), 5000);
  
// => []
// => ["yes", "yes", "no", "yes", "no"]

Hey, what happened here?

We invoked the callApi function 5 times but once we tried to log the array arr value, we still got the empty array back.
If we wait 5 seconds, we get the desired array.
That’s because all the HTTP requests we made were asynchronous, so each of them takes different time to give us a response. We don’t know how long a service will take to give us back a response.

So what can we do?

async/await solves this problem

async function getApiResponseArray() {
  const arr = [];
  
  for (let i = 0; i < 5; i++) {
    const response = await fetch('https://yesno.wtf/api');
    arr.push(response.body.answer);
  }
  
  console.log(arr);
}
  
getApiResponseArray();
  
// => ["no", "yes", "no", "no", "no"]

Declaring an async function, we are able to use the await keyword.
As you can see, we’re not using then anymore, as we do not longer need it.
Inside of our for loop, we’re assigning the fetch response to a constant value, then pushing it into an array.
When the loop finishes, we are able to log our arr array.

Let’s take another example:

import fs from "fs";
  
fs.readFile("./todoList.txt", (err, data) => {
  if (err) {
    console.log("Ooops we got an error!", err);
  } else {
    console.log(data);
  }
});

console.log(data);
  
// => "Go to work, have a nap, eat pizza"
// => Uncaught ReferenceError: data is not defined.

Let’s assume we have a todoList.txt file and we want to read its content.
As you can see, using the fs Node API we have to use a callback function which returns an error (for example if file does not exist) and the file content.
We won’t be able to get the file content outside the callback.

import fs from "fs";

function read(filePath) {
  return new Promise((resolve, reject) => {
    fs.readFile(filePath, (err, data) => {
      if (err) {
        reject("Ooops we got an error!", err);
      } else {
        resolve(data);
      }
    });
  }
} 


read("./todoList.txt")
  .then((data)  => console.log(data))
  .catch((err)) => console.err(err))

console.log(data);
  
// => "Go to work, have a nap, eat pizza"
// => Uncaught ReferenceError: data is not defined.

Wrapping the fs API inside a Promise will allow us to use then/catch, but we can’t still use data outside the Promise.
Let’s make a bit of refactoring:

import fs from "fs";

function read(filePath) {
  return new Promise((resolve, reject) => {
    fs.readFile(filePath, (err, data) => {
      if (err) {
        reject("Ooops we got an error!", err);
      } else {
        resolve(data);
      }
    });
  }
}
  
async function readAndPrint() {
  const data = await read("./todoList.txt");
  console.log(data);
}
  
readAndPrint();
// => "Go to work, have a nap, eat pizza"

Wow! We’re able to use data whenever we want inside the readAndPrint function!
And what if we got an error? How can we handle it?

import fs from "fs";

function read(filePath) {
  return new Promise((resolve, reject) => {
    fs.readFile(filePath, (err, data) => {
      if (err) {
        reject("Ooops we got an error!", err);
      } else {
        resolve(data);
      }
    });
  }
}
  
async function readAndPrint() {
  try {
    const data = await read("./wrong-file-path.txt");
    console.log(data);
  } catch(err) {
    console.error("File does not exist");
  }
}
  
readAndPrint();
// => "File does not exist"

Old school try/catch will help you.

But are async/await really useful?

Imagine the following scenario:

async function getUserData() {
  const firstName = await getFirstName();
  const lastName  = await getLastName();
  const age       = await getAge();
  
  console.log(`My name is ${firstName} ${lastName} and I am ${age} years old!`);
}
  
getUserData();
// => "My name is John Doe and I am 24 years old!"

How would you handle the situation above using Promises or callbacks?
I know, it’s just a little example, but imagine you have to get data from different webservices. It would be harder without async/await!

Did you like this article? Consider becoming a Patron!

This article is CC0 1.0 (Public Domain) licensed.