【问题标题】:Is Javascript forEach sync/async?Javascript forEach 是同步/异步吗?
【发布时间】:2017-02-10 08:27:52
【问题描述】:

我正在阅读 this article here 关于在 forEach 中调用异步函数的内容,我自己做了一个小实验。 虽然效果很好,但我注意到一些与我不太一样的东西预计...

编辑:注释掉对上述文章的引用,因为它只会误导读者并带来困惑。我主要关心的是一般的 Javascript 循环,与参考文章讨论的异步函数无关。

在代码中,我有一个这样的控制台输出:

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

位于循环之后
我认为当它位于循环下方时会立即执行,尤其是当数组相对较大时。

但是,我得到的是这样的:

[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!

所以,这是我的问题....
为什么我的[1] 输出等待循环结束?

我在其他浏览器上尝试了代码(除了我通常使用的 Chrome),我还尝试使用 mapfor 来查看是否得到不同的结果,但它们都是一样的...... 拜托,我需要对此进行解释......这是预期的行为吗?

注意:我说的是浏览器执行,不是 Node.js 这里

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

已解决:这是基于我对 Javascript 语言的偏见。我认为 Javascript 不会等待 forEachfor 结束,但它与 PHP 或 Perl 等其他语言没有什么不同。它实际上是在等待循环结束。

使用普通的 for 循环,它将像任何其他语言的普通 for 循环一样运行。 – 哈日克

编辑:编辑:我找到了确切的答案here

循环在 Node.js 和 JavaScript 中是同步的,同步代码始终运行到完成。因此,如果您不调用异步函数,您可以放心,您的代码在完成之前不会被中断。

【问题讨论】:

  • “为什么我的 [1] 输出要等待循环结束?” 不能正确解释问题。预期的结果是什么?
  • .forEach() 本身并不是异步的。它的function 参数是一个迭代器而不是一个回调。而且,您的示例不会反过来调用任何异步函数,就像本文所讨论的那样。
  • 您可以尝试除console 之外的其他方法来打印输出吗?或将结果推送到数组并在循环后记录。 console.log 可以在浏览器优化中出人意料地工作。
  • 请注意,在您链接的问答中讨论的async.forEach()(现为async.each())与Array's forEach() method 的功能不同。两者都不是异步的,但前者更适合与它一起使用的异步操作。
  • 谢谢大家。好吧,我知道引用的文章是关于 async 库的,我可能不应该首先引用这篇文章......我正在讨论一般的 forEach 并且甚至没有尝试调用任何外部异步函数.很抱歉模棱两可......所以,问题是,为什么循环外的console.log(循环右下方)不是在循环期间而是在循环执行之后执行?目前,我接受了 sabithpocker 的建议,并没有使用 console.log 编写代码,但结果保持不变......

标签: javascript arrays asynchronous browser foreach


【解决方案1】:

arr.forEach 不是异步的。它在功能上与 for 循环非常相似,并且在循环结束之前不会返回。

你的代码

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

大致等于

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

显然应该在[1]之前执行[2]和[3]

【讨论】:

  • 谢谢,@khazhyk。如果是 PHP 或 Perl,则每行按顺序执行,最后执行 [1]。但是,使用 Javascript,在循环期间不会执行 [1] 吗?尤其是当我们有一个巨大的数组时?
  • 不,在这种情况下,javascript 的功能完全相同。
  • 如果您使用像您链接的假设文章中那样的异步库,您可以获得您似乎想要的行为,但是使用正常的 for 循环,它将像正常的 for 循环一样运行任何其他语言。
【解决方案2】:

最佳答案在这里 https://stackoverflow.com/a/5050317/7668448

但这里有一些解释:

forEach阻塞同步,它只是for() {} 代码包装器。。 p>

forEachArray 原型链 的一部分。当像这样someArray.forEach() 调用它时,函数 forEach 在内部将获得指向 someArraythis 指针(这就是原型链的工作方式) .

我们将一些函数作为参数传递给它,我们通过this获得数组,我们用for循环遍历数组,并且在每次迭代中我们调用传递的函数, 带有来自 for 的参数和数组。 ====> func.call(this, this[i], i); (以及为什么我们一开始有值,而索引在第二个位置,它可能是相反的 hhh (但这样更好)。

简而言之,这就是 ForEach 的工作方式。我建议检查我指出的所有答案。

【讨论】:

    猜你喜欢
    • 2012-05-21
    • 1970-01-01
    • 1970-01-01
    • 2012-06-16
    • 1970-01-01
    • 2020-01-14
    • 2019-04-20
    • 2020-10-03
    • 2016-08-13
    相关资源
    最近更新 更多