JavaScript wait for function to finish tutorial

Let's learn how to wait for a function to finish in JavaScript

Posted on July 05, 2021


JavaScript code execution is asynchronous by default, which means that JavaScript won’t wait for a function to finish before executing the code below it.

For example, consider the following function calls:

function first() {
  console.log("1st");
}

function second() {
  console.log("2nd");
}

function third() {
  console.log("3rd");
}

first();
second();
third();

JavaScript will execute your code from top to bottom, which means the output will be as shown below:

1st
2nd
3rd

Now let’s delay the call to the first() function by putting the call inside a setTimeout() method as follows:

setTimeout(function () {
  first();
}, 0);
second();
third();

The result would be as follows:

2nd
3rd
1st

The code example above used setTimeout() method to make a point. In a real web application, the setTimeout() function may be replaced with a call to the fetch() function to retrieve important information for your app operation.

JavaScript won’t wait for your fetch() function call to return a response before executing the code below it:

let response = fetch("<Your API URL>");
console.log(response); // undefined

To handle the asynchronous nature of JavaScript executions, you can use one of the three available methods to wait for a function to finish:

  • Using callback functions
  • Using Promise object
  • Using async/await keywords

This tutorial will help you learn all three methods, starting from using callback functions

Wait for function to finish using callback functions

A callback function is a regular JavaScript function that you pass to another function. The callback function will later be called by the accepting function.

It’s easier to understand a callback function through an example. Consider the following code:

function first(callback) {
  console.log("1st");
  // call the callback function
  callback();
}

function second() {
  console.log("2nd");
}

first(second);

The function that receives a callback function is free to call it anytime during its function execution. It’s very common to see a callback function gets called right at the end of the receiving function’s process.

By using the callback pattern, you can pass the next function to call as a callback to the previous function. Then, you need to instruct the function that receives a callback to call that function at the proper moment.

To continue with the previous example, you can add a callback parameter to first() and second() function and sequentially call the functions inside the setTimeout() method:

function first(callback) {
  console.log("1st");

  callback();
}

function second(callback) {
  console.log("2nd");

  callback();
}

function third() {
  console.log("3rd");
}

setTimeout(function () {
  first(function () {
    second(function () {
      third();
    });
  });
}, 0);

Please keep in mind that you need to pass a function reference as a callback function. A function reference is the name of the function without the parentheses ().

That’s why the callback functions passed above use an extra function(){} to wrap the call to the next function.

Passing the functions along with the callback parameter as shown below will cause an error:

// never do this
setTimeout(first(second(third)), 0);

The callback pattern allows you to wait for a function to finish before executing the next piece of code.

But as you see from the examples above, it requires you to edit the functions so that it can accept a callback function.

Furthermore, the callback pattern is not intuitive and is prone to cause callback hell where the next piece of code is nested inside the previous one.

The setTimeout() call above is one example of a callback hell:

setTimeout(function () {
  first(function () {
    second(function () {
      third();
    });
  });
}, 0);

The more complex your code is, the harder it will be to understand and tweak your code.

That’s why you should resort to the callback pattern only when you run out of other options. Now let’s learn how to use Promise object to wait for a function to finish.

Wait for function to finish using Promise object

A Promise is an object of JavaScript language that represents a token of eventual fulfillment. A promise starts in a pending state and may be resolved or rejected depending on the process that takes place inside that promise.

A typical promise implementation looks as follows:

function first(isTrue) {
  return new Promise((resolve, reject) => {
    if (isTrue) {
      resolve("Promise resolved");
    } else {
      reject("Promise rejected");
    }
  });
}

Depending on the result of the Promise, either the resolve() or the reject() function will be called to end the pending state.

When you call a function that returns a Promise, you need to chain the function call with then() and catch() functions as shown below:

first(true)
  .then((res) => console.log(res))
  .catch((err) => console.log(err));

The resolve() function corresponds to the then() function, while reject() corresponds to the catch() function.

Using the promise pattern, you can call your functions sequentially by placing the next function call inside the then() method.

Take a look at the code below:

function first() {
  return new Promise((resolve) => {
    console.log("1st");
    resolve();
  });
}

function second() {
  return new Promise((resolve) => {
    console.log("2nd");
    resolve();
  });
}

function third() {
  console.log("3rd");
}

first().then(second().then(third()));

While you need to pass function reference when using the callback pattern, the promise chain pattern allows you to call the function right inside then() method.

The promise pattern allows you to sequentially call your functions while avoiding the callback pattern.

Modern JavaScript functions like fetch() already returns Promise object, so you can chain the fetch() function call with then() and catch() functions:

fetch("<Your API URL>")
  .then((response) => console.log(response))
  .catch((error) => console.log(error));

Now that you’ve learned how Promise works, it’s time to learn about async/await keywords, which is an addition to the promise-based pattern.

Wait for function to finish using async/await keywords

The async/await keywords are used to replace the promise chain pattern with the async/await pattern.

As you already know from the Promise explanation above, you need to chain the call to the function that returns a Promise using then/catch functions.

The await keyword allows you to wait until the Promise object is resolved or rejected:

await first();
second();

However, the await keyword must be used inside an async function. A function declared with the async keyword avoids the need to write a promise chain pattern.

Here’s the full example of using async/await on functions that return Promise objects:

function first() {
  return new Promise((resolve) => {
    console.log("1st");
    resolve();
  });
}

function second() {
  return new Promise((resolve) => {
    console.log("2nd");
    resolve();
  });
}

function third() {
  console.log("3rd");
}

async function fnAsync() {
  await first();
  await second();
  third();
}

fnAsync();

To store the value returned by the Promise object, you can assign the await call to a variable as shown below:

function first(isTrue) {
  return new Promise((resolve, reject) => {
    if (isTrue) {
      resolve("Promise resolved");
    } else {
      reject("Promise rejected");
    }
  });
}

async function fnAsync() {
  let response = await first(true);
  console.log(response); // "Promise resolved"
}

fnAsync();

To handle the reject() response using await, you need to wrap the call using try/catch block:

function first(isTrue) {
  return new Promise((resolve, reject) => {
    if (isTrue) {
      resolve("Promise resolved");
    } else {
      reject("Promise rejected");
    }
  });
}

async function fnAsync() {
  try {
    let response = await first(false);
    console.log(response); // "Promise resolved"
  } catch (err) {
    console.log(err);
  }
}

fnAsync();

Without the try/catch block, the reject() call will cause UnhandledPromiseRejection error.

Conclusion

Modern JavaScript allows you to wait for a function to finish before executing the next piece of code.

While the callback pattern works, it introduces a high level of complexity that results in a callback hell.

The Promise based asynchronous call improves the callback pattern, allowing you to sequentially call your functions while keeping your code intuitive and less error-prone.

Additionally, you can use the async/await keywords to remove the promise chain pattern from your code.

Now you’ve learned how to wait for a function to finish using JavaScript. Great work! 😉

Related articles:

Grab the free JavaScript book today 👍

Learn the building blocks of JavaScript programming language like data types, functions, objects, arrays and classes.

Use the knowledge from the book to build a small but solid program.

Learn more
JavaScript Introduction Book