【问题标题】:JavaScript, Node.js: is Array.forEach asynchronous?JavaScript、Node.js:Array.forEach 是异步的吗?
【发布时间】:2016-08-13 20:24:45
【问题描述】:

我有一个关于 JavaScript 的原生 Array.forEach 实现的问题:它的行为是异步的吗? 例如,如果我调用:

[many many elements].forEach(function () {lots of work to do})

这将是非阻塞的吗?

【问题讨论】:

标签: javascript arrays asynchronous foreach node.js


【解决方案1】:

不,它正在阻塞。看看specification of the algorithm

不过,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);
    }
  };
}

如果您必须为每个元素执行大量代码,则应考虑使用不同的方法:

function processArray(items, process) {
    var todo = items.concat();

    setTimeout(function() {
        process(todo.shift());
        if(todo.length > 0) {
            setTimeout(arguments.callee, 25);
        }
    }, 25);
}

然后调用它:

processArray([many many elements], function () {lots of work to do});

这将是非阻塞的。示例取自High Performance JavaScript

另一个选项可能是web workers

【讨论】:

  • 如果您使用的是 Node.js,还可以考虑使用 process.nextTick 而不是 setTimeout
  • 从技术上讲,forEach 并不是“阻塞”的,因为 CPU 永远不会进入睡眠状态。它是同步且受 CPU 限制的,当您期望节点应用程序响应事件时,这感觉就像“阻塞”。
  • async 在这里可能是更合适的解决方案(实际上刚刚看到有人发布了这个答案!)。
  • 我相信这个答案,但在某些情况下似乎是错误的。 forEach 确实 not 阻塞 await 语句,您应该使用 for 循环:stackoverflow.com/questions/37962880/…
  • @Richard:当然。您只能在 async 函数中使用 await。但是forEach 不知道什么是异步函数。请记住,异步函数只是返回承诺的函数。你会期望forEach 处理回调返回的承诺吗? forEach 完全忽略回调的返回值。如果它本身是异步的,它只能处理异步回调。
【解决方案2】:

Array.forEach 用于计算不等待的东西,并且在事件循环中使计算异步没有任何好处(如果您需要多核计算,webworkers 添加多处理)。如果您想等待多个任务结束,请使用计数器,您可以将其包装在信号量类中。

【讨论】:

    【解决方案3】:

    在 Node 中进行非常繁重的计算有一个常见的模式,它可能适用于你......

    节点是单线程的(作为一个深思熟虑的设计选择,请参阅What is Node.js?);这意味着它只能使用一个内核。现代机器有 8 个、16 个甚至更多内核,因此这可能会使 90% 以上的机器处于空闲状态。 REST 服务的常见模式是为每个核心启动一个节点进程,并将这些进程置于本地负载均衡器(如 http://nginx.org/)之后。

    分叉一个孩子 - 对于您正在尝试做的事情,还有另一种常见的模式,即分叉一个子进程来完成繁重的工作。好处是子进程可以在后台进行大量计算,而您的父进程响应其他事件。问题是你不能/不应该与这个子进程共享内存(不是没有很多扭曲和一些本机代码);你必须传递消息。如果您的输入和输出数据的大小与必须执行的计算相比较小,这将非常有效。您甚至可以启动一个子 node.js 进程并使用您之前使用的相同代码。

    例如:

    var child_process = require('child_process'); 函数run_in_child(数组,cb){ var process = child_process.exec('node libfn.js', function(err, stdout, stderr) { var 输出 = JSON.parse(stdout); cb(错误,输出); }); process.stdin.write(JSON.stringify(array), 'utf8'); process.stdin.end(); }

    【讨论】:

    • 明确一点...节点不是单线程的,但 JavaScript 的执行是。 IO 和不在单独线程上运行的东西。
    • @Brad - 也许吧。这取决于实现。通过适当的内核支持,Node 和内核之间的接口可以是基于事件的——kqueue (mac)、epoll (linux)、IO 完成端口(windows)。作为后备,线程池也可以工作。不过,您的基本观点是正确的。低级 Node 实现可能有多个线程。但他们永远不会直接将它们暴露给 JS 用户空间,因为这会破坏整个语言模型。
    • 正确,我只是澄清一下,因为这个概念让很多人感到困惑。
    【解决方案4】:

    如果您需要异步友好版本的 Array.forEach 和类似版本,可以在 Node.js 的“异步”模块中找到它们:http://github.com/caolan/async ...另外,这个模块也可以在浏览器中使用。

    async.each(openFiles, saveFile, function(err){
        // if any of the saves produced an error, err would equal that error
    });
    

    【讨论】:

    • 如果您需要确保一次只为一项(按集合顺序)运行异步操作,则必须改用eachSeries
    • @JohnKennedy 我以前见过你!
    【解决方案5】:

    这是一个无需第三方库即可使用的简短异步函数

    Array.prototype.each = function (iterator, callback) {
        var iterate = function () {
                pointer++;
                if (pointer >= this.length) {
                    callback();
                    return;
                }
                iterator.call(iterator, this[pointer], iterate, pointer);
        }.bind(this),
            pointer = -1;
        iterate(this);
    };
    

    【讨论】:

    • 这是怎么异步的? AFAIK #call 会立即执行吗?
    • 当然是马上,但是你有回调函数可以知道所有迭代何时完成。这里的“迭代器”参数是一个带有回调的节点式异步函数。类似于 async.each 方法
    • 我看不出这是异步的。 call 或 apply 是同步的。有一个回调不会使它异步
    • 在 javascript 中,当人们说异步时,他们的意思是代码执行不会阻塞主事件循环(也就是它不会使进程卡在一行代码中)。仅仅放置一个回调不会使代码异步,它必须利用某种形式的事件循环释放,例如 setTimeout 或 setInterval。因为在你等待的时候,其他代码可以不间断地运行。
    【解决方案6】:

    编辑 2018 年 10 月 11 日: 看起来下面描述的标准很有可能无法通过,请考虑 pipelineing 作为替代方案(行为不完全相同,但方法可以在类似的庄园中实现)。

    这正是我对 es7 感到兴奋的原因,将来您将能够执行类似以下代码的操作(某些规范不完整,因此请谨慎使用,我会尽量保持最新)。但基本上使用 new :: bind 运算符,您将能够在对象上运行方法,就好像对象的原型包含该方法一样。例如 [Object]::[Method] 通常你会调用 [Object].[ObjectsMethod]

    请注意今天(2016 年 7 月 24 日)并让它在所有浏览器中工作,您需要为以下功能转换代码:导入/导出箭头函数PromisesAsync / Await,最重要的是函数绑定。如果需要,下面的代码可以修改为仅使用函数绑定,所有这些功能今天都可以通过使用 babel 整齐地使用。

    YourCode.js(其中'很多工作要做'必须简单地返回一个承诺,在异步工作完成时解决它。)

    import { asyncForEach } from './ArrayExtensions.js';
    
    await [many many elements]::asyncForEach(() => lots of work to do);
    

    ArrayExtensions.js

    export function asyncForEach(callback)
    {
        return Promise.resolve(this).then(async (ar) =>
        {
            for(let i=0;i<ar.length;i++)
            {
                await callback.call(ar, ar[i], i, ar);
            }
        });
    };
    
    export function asyncMap(callback)
    {
        return Promise.resolve(this).then(async (ar) =>
        {
            const out = [];
            for(let i=0;i<ar.length;i++)
            {
                out[i] = await callback.call(ar, ar[i], i, ar);
            }
            return out;
        });
    };
    

    【讨论】:

      【解决方案7】:

      npm 上有一个包方便asynchronous for each loops

      var forEachAsync = require('futures').forEachAsync;
      
      // waits for one request to finish before beginning the next 
      forEachAsync(['dogs', 'cats', 'octocats'], function (next, element, index, array) {
        getPics(element, next);
        // then after all of the elements have been handled 
        // the final callback fires to let you know it's all done 
        }).then(function () {
          console.log('All requests have finished');
      });
      

      还有另一个变体forAllAsync

      【讨论】:

        【解决方案8】:

        例如,甚至可以编写这样的解决方案:

         var loop = function(i, data, callback) {
            if (i < data.length) {
                //TODO("SELECT * FROM stackoverflowUsers;", function(res) {
                    //data[i].meta = res;
                    console.log(i, data[i].title);
                    return loop(i+1, data, errors, callback);
                //});
            } else {
               return callback(data);
            }
        };
        
        loop(0, [{"title": "hello"}, {"title": "world"}], function(data) {
            console.log("DONE\n"+data);
        });
        

        另一方面,它比“for”慢得多。

        否则,优秀的 Async 库可以做到这一点:https://caolan.github.io/async/docs.html#each

        【讨论】:

          【解决方案9】:

          这是一个小例子,你可以运行它来测试它:

          [1,2,3,4,5,6,7,8,9].forEach(function(n){
              var sum = 0;
              console.log('Start for:' + n);
              for (var i = 0; i < ( 10 - n) * 100000000; i++)
                  sum++;
          
              console.log('Ended for:' + n, sum);
          });
          

          它会产生这样的结果(如果花费的时间太少/太多,增加/减少迭代次数):

          (index):48 Start for:1
          (index):52 Ended for:1 900000000
          (index):48 Start for:2
          (index):52 Ended for:2 800000000
          (index):48 Start for:3
          (index):52 Ended for:3 700000000
          (index):48 Start for:4
          (index):52 Ended for:4 600000000
          (index):48 Start for:5
          (index):52 Ended for:5 500000000
          (index):48 Start for:6
          (index):52 Ended for:6 400000000
          (index):48 Start for:7
          (index):52 Ended for:7 300000000
          (index):48 Start for:8
          (index):52 Ended for:8 200000000
          (index):48 Start for:9
          (index):52 Ended for:9 100000000
          (index):45 [Violation] 'load' handler took 7285ms
          

          【讨论】:

          • 即使您编写 async.foreach 或任何其他并行方法也会发生这种情况。因为for循环不是IO过程Nodejs总会同步做。
          【解决方案10】:

          使用bluebird库的Promise.each

          Promise.each(
          Iterable<any>|Promise<Iterable<any>> input,
          function(any item, int index, int length) iterator
          ) -> Promise
          

          这个方法迭代一个数组,或者一个数组的promise,它包含promise(或者promise和values的混合)和给定的iterator函数和签名(value, index, length) 其中 value 是输入数组中相应 Promise 的解析值。 迭代连续发生。 如果迭代器函数返回一个 promise 或 thenable,则在继续下一次迭代之前等待 promise 的结果。如果输入数组中的任何 Promise 被拒绝,则返回的 Promise 也会被拒绝。

          如果所有迭代都成功解析,Promise.each 解析为未修改的原始数组。但是,如果一次迭代被拒绝或出错,Promise.each 会立即停止执行并且不再处理任何进一步的迭代。在这种情况下返回错误或拒绝值而不是原始数组。

          这个方法是用来处理副作用的。

          var fileNames = ["1.txt", "2.txt", "3.txt"];
          
          Promise.each(fileNames, function(fileName) {
              return fs.readFileAsync(fileName).then(function(val){
                  // do stuff with 'val' here.  
              });
          }).then(function() {
          console.log("done");
          });
          

          【讨论】:

            【解决方案11】:

            虽然 Array.forEach 不是异步的,但你可以获得异步的“最终结果”。下面的例子:

            function delayFunction(x) {
                return new Promise(
                    (resolve) => setTimeout(() => resolve(x), 1000)
                );
            }
            
            [1, 2, 3].forEach(async(x) => {
                console.log(x);
                console.log(await delayFunction(x));
            });

            【讨论】:

              【解决方案12】:

              这些代码 sn-p 会让你更好地理解 forEach 和 forOf 的比较。

              /* eslint-disable no-console */
              async function forEachTest() {
                  console.log('########### Testing forEach ################ ')
                  console.log('start of forEachTest func')
                  let a = [1, 2, 3]
                  await a.forEach(async (v) => {
                      console.log('start of forEach: ', v)
                      await new Promise(resolve => setTimeout(resolve, v * 1000))
                      console.log('end of forEach: ', v)
                  })
                  console.log('end of forEachTest func')
              }
              forEachTest()
              
              
              async function forOfTest() {
                  await new Promise(resolve => setTimeout(resolve, 10000)) //just see console in proper way
                  console.log('\n\n########### Testing forOf ################ ')
                  console.log('start of forOfTest func')
                  let a = [1, 2, 3]
                  for (const v of a) {
                      console.log('start of forOf: ', v)
                      await new Promise(resolve => setTimeout(resolve, v * 1000))
                      console.log('end of forOf: ', v)
                  }
                  console.log('end of forOfTest func')
              }
              forOfTest()

              【讨论】:

                猜你喜欢
                • 2016-08-03
                • 2017-02-10
                • 1970-01-01
                • 2019-07-04
                • 2012-03-29
                • 2020-01-14
                • 1970-01-01
                • 1970-01-01
                相关资源
                最近更新 更多