Loop actions can vary, and within the loop, there are different actions that we might work with. Some of these actions might be synchronous and some can be asynchronous.
For scenarios of asynchronous actions within loops (especially for loops), we need to make sure we allows them finish before we can move on to the next iteration.
In this article, we will detail how to wait for loop to finish in Node.js (JavaScript) using two methods.
Introducing Promises
Before we move into Promises
, we need to discuss callbacks. Callbacks are functions passed to other functions and are executed when a specific code runs, so the functions that will be passed will have to finish their actions before being used by the other function.
A simple illustration can be seen below where we pass the help
function as an argument of the cli
function.
function help(tool) {
console.log("The " + tool + " documentation is here thus");
}
function cli(callback) {
let tool = prompt("Need Help? Enter Tool Name: ");
console.log("Checking the database for the tool passed");
callback(tool);
}
cli(help);
When we pass dgf
within the prompt, our output becomes
Checking the database for the tool passed
The dgf documentation is here thus
JavaScript is asynchronous by nature so any long-running task will be queued, and will move on to other tasks. Therefore, with task such as networks calls, we can deal with undefined
values using callbacks.
However, code with multiple callbacks can easily become messy and hard to maintain. This phenomenon is called callback hell. To deal with this, Promises
are helpful and better to use.
The Promise
object represents the eventual completion or failure of a JavaScript asynchronous operation and its resulting value, and it returns a Promise
that will provide the resulting value when its needed within the code later.
To illustrate Promises
, we can create a code that takes a URL and processes its response. The resolve
deals for situation where the code succeeds, and the reject
kicks in where the code fails.
function getData(url) {
return new Promise((resolve, reject) => {
if (!url) {
reject("No URL provided");
}
const xhr = new XMLHttpRequest();
xhr.open("GET", url);
xhr.send();
xhr.onload = function () {
if (xhr.status === 200) {
resolve(xhr.responseText);
} else {
reject(xhr.status);
}
};
});
}
const url = prompt("Enter a URL");
getData(url)
.then((result) => {
console.log("Success!"); // Runs on success
console.log(result);
}).catch(status => {
console.log(`An error with status code ${status} occurred: `); // Runs on error
});
When we pass the https://reqres.in/api/users?page=2
as the input to the prompt, the output becomes
Success!
{"page":2,"per_page":6,"total":12,"total_pages":2,"data":
...
The .then()
will be triggered when the getData
function is successful, but the .catch()
comes in when there is an error.
For our case scenario of dealing with waiting for loop to finish in Node.js, we will need this basic understanding of Promisies
and asynchronous
programming.
Using async/await to wait for loop to finish
With async/await
keywords, we can enable Promise-based
behaviour and asynchronous execution within our code. So, for use to wait for the loop to finish, we can define an async
function and wait its result within.
Method 1: for...of loop
If we have iterables that we need to go over with a for loop but will need to carry out some asynchronous operations on (such as a network call), we can define our iteration using the below code
function wrap() {
return new Promise((resolve) => setTimeout(resolve, 500));
}
async function logging(num) {
await wrap();
console.log(num);
}
async function printElement(array) {
for (const num of array) {
await logging(num);
}
console.log("Operation Completed.");
}
printElement([1, 2, 4]);
Output
1
2
4
Operation Completed.
The wrap()
function holds the Promise
object that will deal with the resolve
situation within the code. Afterwards, we define an asynchronous
function (logging
) that logs the numbers we are iterating over but we await
the wrap()
function before logging the numbers.
From here, we define another asynchronous
function (printElement
) that will loop over the array and execute the logging
function.
Method 2: Use Promise.all
Promise.all
method takes an iterable of promises and returns a single Promise that will be fulfilled only when all the promises within it has been fulfilled. Since we have an iterable, we can make use of this method to initialize Promise
for each, and then the method will return a Promise when all the Promises for each iteration has been fulfilled.
To achieve that, we can adjust the printElement
function as thus by mapping the array to promises and waiting for all promises to be resolved before moving one.
async function printElement(array) {
const promises = array.map(logging);
await Promise.all(promises);
console.log("Operation Completed.")
}
Output
1
2
4
Operation Completed
Summary
To wait for all operations within a loop iteration to finish, Promises
are your best bet to dealing with such situations. Each method are effective in dealing with such, however, the Promise.all
method which will run all the attached functions in parallel can make for a cumbersome approach in very large arrays.
References
Promise.all() - JavaScript | MDN (mozilla.org)
Promise - JavaScript | MDN (mozilla.org)