minagawah minagawah - 2 months ago 16
Javascript Question

Is Javascript forEach sync/async?

I was reading this article here about calling an asynchronous function within

forEach
and I did a little experiment for my own. Although it worked fine, I noticed something which wasn't quite what I expected...

EDIT: Commented out the reference to the original article for it brings nothing but confusions. Calling async functions was my initial aim, but later came the bellow question of mine regarding
Array.forEach
function.


In the code, I have a console output like this:

timer.show('[0] res[' + lastindex + ']: '
+ (typeof res[lastindex] != 'undefined' ? res[lastindex] : 'NA'));


which is located RIGHT AFTER THE LOOP.

I thought it would be executed immediately when it is located just bellow the loop, especially when the array is relatively huge.

But, what I got was this:

[created] array with 550000
[1,2,3]
[4,5,6]
[7,8,9]
....
[0] res[549999]: NA (elapsed: 4 msec)
[2] res[549998]: 4949988 (elapsed: 223 msec)
[3] res[549999]: 4949997 (elapsed: 224 msec)
[1] res[549999]: 4949997 (elapsed: 224 msec) <--- HERE
[4] res[549999]: 4949997 (elapsed: 236 msec)
done!


So, here's my question....

Why is it that my
[1]
output waits for the loop to end?


I try the code on other browsers (other than Chrome which I usually work with), I also tried using
map
and
for
to see if I get different results, but they were all the same...

Please, I need explanations on this.... is this intended behavior?

Note: I'm talking about browser execution, not Node.js here



(fn => {
// Just creating a huge array for the test.
let arr = [];
let max = 550000; // This seems appropriate
// for stackoverflow snippet execution.
// (or around 10000000 for my browser)
let n = 1;
for (let i=0; i<max; i++) {
arr.push([n++, n++, n++]);
if ((i + 1) >= max) {
fn(arr);
}
}
})(arr => {
// Now, the test begins!
let timer = simple_timer_factory();
let timer_id = timer.beg();
let size = arr.length;
let lastindex = (size - 1);
console.log('[created] array with ' + size);
console.log(' ' + JSON.stringify(arr[0]));
console.log(' ' + JSON.stringify(arr[1]));
console.log(' ' + JSON.stringify(arr[2]));
console.log(' ....');

let res = [];
// Peeping the last element even before the loop begins.
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'));

// To double make sure, we use "setInterval" as well to watch the array.
let id = window.setInterval(() => {
let lastindex2 = (res.length - 1);
if (lastindex2 >= lastindex) {
window.clearInterval(id);
id = void 0;
timer.show('[4] res[' + lastindex2 + ']: ' + res[lastindex2]);
timer.end(timer_id);
console.log('done!');
}
}, 10);
});

/**
* This has nothing to do with the main question here.
* It will keep track of the time elapsed.
* @returns {Object}
*/
function simple_timer_factory() {
var init, prev, curr;
return Object.create({
beg(fn) {
init = prev = curr = Date.now();
( window.requestAnimationFrame ||
window.webkitRequestAnimationFrame ||
function(tick){
return window.setTimeout(
tick, Math.ceil(1000 / 30)
);
}
)(fn || function(){});
},
snapshot() {
curr = Date.now();
return {
prev,
curr,
elapse: curr - init,
delta: curr - prev
};
},
show(msg) {
console.log(
(msg ? (msg + ' '): '')
+ '(elapsed: '
+ this.snapshot().elapse + ' msec)');
},
end(timer_id) {
prev = curr = void 0;
( window.cancelAnimationFrame ||
window.webkitCancelAnimationFrame ||
function(id){
if (id) {
window.clearTimeout(id);
}
}
)(timer_id);
}
});
}




Answer

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]

Comments