0

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 article above for it just misleads readers and brings confusions. My primary concern is about Javascript loops in general, and has nothing to do with async functions as the referred article discusses.

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);
            }
        });
    }

SOLVED: It was based on my prejudice about Javascript language. I thought Javascript does not wait for forEach or for to end, but it was no different than other languages like PHP or Perl. It actually waits for the loop to end.

with a normal for loop, it will function like a normal for loop in any other language. – khazhyk

EDIT: EDIT: I found the exact answer here.

Loops are synchronous in Node.js and JavaScript and synchronous code always runs to completion. So if you're not making calls to asynchronous functions you can rest assured that your code will not be interrupted until it finishes.

Community
  • 1
  • 1
minagawah
  • 95
  • 2
  • 9
  • _"Why is it that my [1] output waits for the loop to end?"_ Not certain interpret Question correctly. What is expected result? – guest271314 Oct 02 '16 at 04:39
  • `.forEach()` isn't itself asynchronous. Its `function` argument is an iterator rather than a callback. And, your example doesn't in turn invoke any asynchronous functions, like the article is discussing. – Jonathan Lonowski Oct 02 '16 at 04:39
  • Can you try something other than `console` to print output? or push results to an array and log it after the loop. `console.log` can work unexpected with browser optimisations. – sabithpocker Oct 02 '16 at 04:41
  • Note that `async.forEach()` (now [`async.each()`](https://caolan.github.io/async/docs.html#.each)), that's discussed in the Q&A you linked, is not the same function as [`Array`'s `forEach()` method](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Array/forEach). Neither are asynchronous on their own, but the former is more prepared for asynchronous operations to be used with it. – Jonathan Lonowski Oct 02 '16 at 04:47
  • Thanks everyone. Well, I am aware the referred article is about `async` library, and I should have not probably refer the article the first place... I am discussing about general `forEach` and am not even attempting to call any external asynchronous function either. Sorry for the ambiguity... So, the question is, why is the `console.log` outside the loop (right bellow the loop) executed not during the loop but after the loop execution? For now, I took sabithpocker's advice and I made the code not using `console.log` but the result remains the same... – minagawah Oct 02 '16 at 05:08
  • @guest271314 My expectation was that `[1]` output comes after `[0]`. Meaning, it outputs a log **RIGHT AFTER THE LOOP BEGINS** or at least during the loop execution. But, in reality, I get the output **AFTER** the loop ends... – minagawah Oct 02 '16 at 05:39

2 Answers2

0

Best answer here https://stackoverflow.com/a/5050317/7668448

But here some explanations:

forEach is blocking synchronous, it's just a wrapper around for() {} code.

forEach is part of Array prototype chain. When called it like this someArray.forEach(), internally the function forEach will get it's this pointer pointing to someArray (that's how the prototype chaining work).

We pass to it some function as argument, we have the array through this, we loop through the array with for, and in each iteration we call the passed function, with the parameter from for and the array. ====> func.call(this, this[i], i); (and here why we have value at first, and the index at second position, it could have been the inverse hhh (but it's better this way)).

In short that's how ForEach work. I advice to check the answer i pointed all in top.

Community
  • 1
  • 1
Mohamed Allal
  • 17,920
  • 5
  • 94
  • 97
-1

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]

khazhyk
  • 1,738
  • 1
  • 18
  • 13
  • Thanks, @khazhyk . If it is PHP or Perl, each line is executed sequentially, and [1] is executed at last. But, with Javascript, wouldn't [1] executed during the loop? Especially when we have a huge array? – minagawah Oct 02 '16 at 05:51
  • no, javascript functions in the exact same manner in this case. – khazhyk Oct 02 '16 at 05:52
  • If you use an asynchronous library like the one in the supposed article you linked, you could get the behavior you seem to desire, however with a normal for loop, it will function like a normal for loop in any other language. – khazhyk Oct 02 '16 at 05:55