Wait for a function to finish before continuing in JavaScript

Reading Time: 4 minutes 🕑

Last updated: May 27, 2022.

Sooner or later, you will come across the need to make JavaScript wait until a function has finished before continuing to execute more code.

In JavaScript this can sometimes be challenging, because JavaScript doesn’t wait for code that it knows will take some time to complete. Instead, it executes the rest of your script and returns to complete it afterwards. But what if you are trying to handle the result in the rest of your script?

In this tutorial, we show you wait for a function to finish before another starts executing by using callbacks and handling promise using async/await syntax.

Table of contents

Example: Executing tasks in order

Sometimes, you won’t have this problem. For example, in the code below, the console.log() inside task1 will always run before the one inside task2:

function task1() {
  // Do something
  console.log("Task 1 complete!");
}

function task2() {
  // Do something else
  console.log("Task 2 complete!");
}

task1()
task2()

But in some cases, this won’t be the case.

For example, if each task is completed with a random time delay using setTimeout(), the completion order will be random, even though task1 is called before task2.

function task1() {
  const wait = Math.round(Math.random()*2000);
  setTimeout(function() {
    console.log("Task 1 complete!");
  },wait)
}

function task2() {
  const wait = Math.round(Math.random()*2000);
  setTimeout(function() {
    console.log("Task 2 complete!");
  },wait)
}

task1()
task2()

This is a contrived example.

In practice, this occurs most commonly when making an API to, for example, get data. Handling of the result should wait until the response from the call is returned, but JavaScript doesn't wait.

In the example below, task2 always completes before task1:

function task1() {
  fetch('https://httpbin.org/get')
  .then(res => res.json())
  .then(data => console.log(data))
}

function task2() {
  // Do something with Fetch result
  console.log("Done handling the Fetch result!");
}

task1()
task2()

This behaviour occurs because when JavaScript comes across some code that it knows involves some waiting time, it skips it, continues processing code, and returns to it when all other code has been processed.

This type of code is know as asynchronous.

Typically, this is any code that makes a HTTP request to get or send data (Fetch, Axios, Jquery's Ajax) and setTimeout() and setInterval().

Solutions to waiting for a function

Callbacks

Callbacks are a way to solve the waiting problem with pure functions.

The idea is that after finishing the task inside a function, you call another function. The function to be called next is passed in as an argument when calling the a function with a callback parameter.

For example, below the executeTasks function runs task1 and task2 in order. Each time a task function is called, a function is passed into it. This function calls the next task.

function task1(callback) {
  fetch('https://httpbin.org/get')
  .then(res => res.json())
  .then(data => console.log(data))
  .then(() => callback())
}

function task2(callback) {
  // Do something with Fetch result
  console.log("Done handling the Fetch result!");
  callback();
}

function executeTasks() {
  task1(function() {
    task2(function() {
      console.log("Task 3 can start here")
    })
  })
}

executeTasks()

As you can see, adding a callback parameter and calling it inside task1 and task2 is straightforward.

But the executeTasks function is messy. In fact, this nested indentation pattern is commonly known as the Pyramid of Doom.

The way to avoid this problem is to use modern JavaScript features: promises handled with async/await.

Promises handled with async/await

To solve this with modern features, place the keyword async before the functions.

Doing this enables the use of the keyword await within them. When used before some code that returns a promise, await prevents further function execution until the promise is returned. A second effect of using async before the functions is any return value will now be a promise.

So now, when the executeTasks function is run, it waits for the return value of task1 before moving on to task2. Though task2 does not return a promise, await is still used in case a return value is added when processing the result.

async function task1() {
  const res = await fetch('https://httpbin.org/get')
    .then(res => res.json())
    .then(data => console.log(data))
  return res;
}

async function task2() {
  // Do something with Fetch result
  console.log("Done handling the Fetch result!");
}

async function executeTasks() {
  await task1();
  await task2();
  console.log("Task 3");
}

executeTasks();

The big advantage here is how cleanly the tasks are handled within the executeTasks function. Unlike with callbacks, there is no nesting indentation, making it easier to read.

Writing your own promise

In the example above, fetch returns a promise. But what if you are dealing with some asynchronous code that doesn't return a promise, such as setTimeout()?

To do so, you are going to want to wrap this process in a promise so that, when it is complete, it returns a promise.

To do so, create a new Promise instance by appending the keyword new before it. Then, pass in a function containing the non-promise-returning asynchronous code.

Finally, call resolve or reject within the function, depending upon whether the process resolved successfully or not. setTimeout works every time, so resolve is called immediately after the log the console.

async function task1() {
  const wait = Math.round(Math.random()*2000);
  const res = await new Promise(function(resolve, reject) {
    setTimeout(function() {
      console.log("Task 1 complete");
      resolve();
    }, wait)
  })
  return res;
}

async function task2() {
  // Do something with Fetch result
  console.log("Task 2 complete");
}

async function executeTasks() {
  await task1();
  await task2();
  console.log("Task 3")
}

executeTasks();

Summary

You will often come across the need to wait for one function to finish before starting another in JavaScript. But this can sometimes be challenging to achieve because JavaScript doesn't wait for asynchronous code – typically, getting and sending data when making an API call.

To handle these situations effectively, it is necessary to employ callback functions or handle the processes with promises handled with async/await syntax.