Wait for a function to finish before continuing in JavaScript
Last updated: October 6, 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.