JavaScript,Node.js:Array.forEach是否异步?


Answers:


392

不,它正在阻止。看一下算法规格

但是,在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

另一个选择可能是网络工作者


37
如果您使用的是Node.js,还可以考虑使用process.nextTick而不是setTimeout
Marcello Bastea-Forte 2011年

28
从技术上讲,forEach不会“阻塞”,因为CPU永远不会进入睡眠状态。它是同步的且受CPU限制,当您期望节点应用程序响应事件时,感觉就像“阻塞”。
戴夫·多普森

3
异步可能是这里更合适的解决方案(实际上只是看到有人将其发布为答案!)。
2014年

6
我相信这个答案,但在某些情况下似乎是错误的。例如,forEach不会阻塞await语句,您应该使用for循环:stackoverflow.com/questions/37962880/…–
理查德(Richard)

3
@理查德:当然。您只能使用await内部async函数。但是forEach不知道什么是异步函数。请记住,异步函数只是返回承诺的函数。您是否希望forEach处理从回调返回的promise?forEach完全忽略回调的返回值。如果它本身是异步的,它将只能处理异步回调。
Felix Kling

80

如果您需要的异步友好版本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
});

2
如果您需要确保一次运行一次异步操作(按照集合的顺序),则必须使用eachSeries
matpop

@JohnKennedy我以前见过你!
Xsmael

16

有一种常见的模式可以在Node中执行非常繁重的计算,这可能适用于您...

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

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

例如:

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

11
只是要清楚...节点不是单线程的,但是JavaScript的执行是。IO和不在单独线程上运行的内容。
Brad 2013年

3
@布拉德-也许吧。那取决于实现。有了适当的内核支持,Node和内核之间的接口可以基于事件-kqueue(mac),epoll(linux),IO完成端口(windows)。作为备用,线程池也可以工作。您的基本观点是正确的。低级Node实现可能具有多个线程。但是它们永远不会直接将它们暴露给JS用户领域,因为那样会破坏整个语言模型。
戴夫·多普森

4
是的,我只是澄清一下,因为这个概念混淆了很多。
布拉德

6

Array.forEach意味着不需要等待就可以进行计算,并且在事件循环中使计算异步化是没有任何收获的(如果需要多核计算,网络工作人员可以添加多处理功能)。如果要等待多个任务结束,请使用计数器,您可以将其包装在信号量类中。


5

编辑2018-10-11:看起来下面描述的标准很有可能无法通过,可以考虑使用流水线作为替代(行为不完全相同,但是方法可以在类似的庄园中实现)。

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

请注意,今天(16年7月24日)执行此操作,并使它在所有浏览器中都有效,您将需要为以下功能转换代码:Import / ExportArrow函数PromisesAsync / Await和最重要的是bind函数。可以将下面的代码修改为仅在必要时使用函数绑定,而今天,通过使用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;
    });
};

1

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

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将立即执行?
吉尔斯·威廉姆斯

1
当然可以,但是您可以使用回调函数来知道何时完成所有迭代。这里的“迭代器”参数是带有回调的节点样式异步函数。它类似于async.each方法
Rax Wunter

3
我看不到这是如何异步的。呼叫或申请是同步的。进行回调不会使其异步
adrianvlupu

在javascript中,当人们说异步时,它们意味着代码执行不会阻塞主事件循环(也就是,它不会使过程停留在一行代码中)。仅仅放置回调不会使代码异步,它必须利用某种形式的事件循环释放,例如setTimeout或setInterval。由于浪费了您等待的时间,因此其他代码可以正常运行。
vasilevich

0

npm上有一个软件包,用于每个循环的轻松异步

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

也是AllAsync的另一个变


0

例如,甚至可以对这样的解决方案进行编码:

 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


0

这是一个小示例,您可以运行对其进行测试:

[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或任何其他并行方法,也会发生这种情况。因为as for循环不是IO进程,所以Node.js将始终同步进行。
Sudhanshu Gaur

-2

使用Promise.each蓝鸟库。

Promise.each(
Iterable<any>|Promise<Iterable<any>> input,
function(any item, int index, int length) iterator
) -> 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");
});
By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.