JavaScript, Node.js: is Array.forEach asynchronous?
No, it is blocking. Have a look at the specification of the algorithm.
However a maybe easier to understand implementation is given on MDN:
if (!Array.prototype.forEach)
{
Array.prototype.forEach = function(fun /*, thisp */)
{
"use strict";
if (this === void 0 || this === null)
throw new TypeError();
var t = Object(this);
var len = t.length >>> 0;
if (typeof fun !== "function")
throw new TypeError();
var thisp = arguments[1];
for (var i = 0; i < len; i++)
{
if (i in t)
fun.call(thisp, t[i], i, t);
}
};
}
If you have to execute a lot of code for each element, you should consider to use a different approach:
function processArray(items, process) {
var todo = items.concat();
setTimeout(function() {
process(todo.shift());
if(todo.length > 0) {
setTimeout(arguments.callee, 25);
}
}, 25);
}
and then call it with:
processArray([many many elements], function () {lots of work to do});
This would be non-blocking then. The example is taken from High Performance JavaScript.
Another option might be web workers.
Is Array.forEach asynchronous?
forEach
is synchronous. Your particular callback function, however, is not. So forEach
synchronously calls your function, which starts its work, once for each entry in the array. Later, the work that started finishes asynchronously, long after forEach
has returned.
The issue is that your callback is async
, not that forEach
is asynchronous.
In general, when you're using an API like forEach
that doesn't do anything with the return value (or doesn't expect a promise as a return value), either:
Don't pass it an
async
function, orEnsure that you handle errors within the function itself
Otherwise, you'll get unhandled errors if something goes wrong in the function.
Or of course:
- Use a
try
/catch
block within theasync
function to catch and handle/report errors within the function itself.
Using async/await with a forEach loop
Sure the code does work, but I'm pretty sure it doesn't do what you expect it to do. It just fires off multiple asynchronous calls, but the printFiles
function does immediately return after that.
Reading in sequence
If you want to read the files in sequence, you cannot use forEach
indeed. Just use a modern for … of
loop instead, in which await
will work as expected:
async function printFiles () {
const files = await getFilePaths();
for (const file of files) {
const contents = await fs.readFile(file, 'utf8');
console.log(contents);
}
}
Reading in parallel
If you want to read the files in parallel, you cannot use forEach
indeed. Each of the async
callback function calls does return a promise, but you're throwing them away instead of awaiting them. Just use map
instead, and you can await the array of promises that you'll get with Promise.all
:
async function printFiles () {
const files = await getFilePaths();
await Promise.all(files.map(async (file) => {
const contents = await fs.readFile(file, 'utf8')
console.log(contents)
}));
}
Is Javascript forEach sync/async?
arr.forEach is not asynchronous. It is functionally very similar to a for loop, and does not return until the loop is over.
Your code
timer.show('[0] res[' + lastindex + ']: '
+ (typeof res[lastindex] != 'undefined' ? res[lastindex] : 'NA'));
arr.forEach((item, i, arr) => {
res.push(item.reduce((a, b) => {
return a + b;
}));
// The element right before the last.
if (i == (lastindex - 1)) {
timer.show('[2] res[' + i + ']: ' + res[i]);
}
// The last element.
if (i == lastindex) {
timer.show('[3] res[' + i + ']: ' + res[i]);
}
});
// Peeping inside the last element before the loop ends!?
timer.show('[1] res[' + lastindex + ']: '
+ (typeof res[lastindex] != 'undefined' ? res[lastindex] : 'NA'));
Is roughly equal to
timer.show('[0] res[' + lastindex + ']: '
+ (typeof res[lastindex] != 'undefined' ? res[lastindex] : 'NA'));
for (i = 0; i < arr.length; i++) {
item = arr[i];
res.push(item.reduce((a, b) => {
return a + b;
}));
// The element right before the last.
if (i == (lastindex - 1)) {
timer.show('[2] res[' + i + ']: ' + res[i]);
}
// The last element.
if (i == lastindex) {
timer.show('[3] res[' + i + ']: ' + res[i]);
}
}
// Peeping inside the last element before the loop ends!?
timer.show('[1] res[' + lastindex + ']: '
+ (typeof res[lastindex] != 'undefined' ? res[lastindex] : 'NA'));
which clearly should execute [2] and [3] before [1]
aynchronous forEach loop returning an array
Your first problem: The output array is empty because you use it before any of promises is executes. You have to await
all promises before using the array.
The second problem: Promises can execute and therefore push
items in (pseudo-)random order. Your output array can get shuffled.
The solution is (1) await
all promises and (2) keep order of them (using Array.prototype.map
):
async function foo(input) {
let output = await Promise.all(input.map(async element => {
return element * 2;
}));
return output;
}
// Call it
let input = [ 1, 2, 3, 4, ]; // This is your input array
foo(input).then(output => console.log(output));
The Promises.all
is async
function that takes array of promises and returns array of their results.
Array.prototype.map
executes function for each item of the array.
More information:
tutorial and article about Promises on MDN
Array.prototype.map
await with array foreach containing async await
Use Array.prototype.map and Promise.all:
async function procesMultipleCandidates (data) {
let generatedResponse = []
await Promise.all(data.map(async (elem) => {
try {
// here candidate data is inserted into
let insertResponse = await insertionInCandidate(elem)
// and response need to be added into final response array
generatedResponse.push(insertResponse)
} catch (error) {
console.log('error'+ error);
}
}))
console.log('complete all') // gets loged first
return generatedResponse // return without waiting for process of
}
Or use a for/of
loop if you don't want the loop run concurrently:
async function procesMultipleCandidates (data) {
let generatedResponse = []
for(let elem of data) {
try {
// here candidate data is inserted into
let insertResponse = await insertionInCandidate(elem)
// and response need to be added into final response array
generatedResponse.push(insertResponse)
} catch (error) {
console.log('error'+ error);
}
}
console.log('complete all') // gets loged first
return generatedResponse // return without waiting for process of
}
showing empty array in after pushing data from foreach into it in asynchronous function
The forEach
function is not async-aware. You will need to use a simple for loop:
for( let i = 0; i < commandbody.length; i++ ) {
let arrayC = await commandsModel.getbyId(command);
cmdArray.push(arrayC);
}
console.log(cmdArray);
This should work if your outer function is marked as async too.
Related Topics
Object Property Name as Number
How to Concatenate a String with a Variable
Why JavaScript Treats a Number as Octal If It Has a Leading Zero
Does React Keep the Order for State Updates
Window.Location.Href and Window.Open () Methods in JavaScript
How to Filter an Array from All Elements of Another Array
What's the Difference Between Window.Location and Document.Location in JavaScript
Check Image Width and Height Before Upload with JavaScript
Dynamically Add Script Tag with Src That May Include Document.Write
Best Way to Iterate Over an Array Without Blocking the Ui
Are Eval() and New Function() the Same Thing
Defining Methods via Prototype VS Using This in the Constructor - Really a Performance Difference
Variable as the Property Name in a JavaScript Object Literal
Is There Any Non-Eval Way to Create a Function with a Runtime-Determined Name
How to Convert an Array of Objects to Object with Key Value Pairs
How to Sandbox JavaScript Running in the Browser
Using JavaScript's Atob to Decode Base64 Doesn't Properly Decode Utf-8 Strings