JavaScript, Node.js: is Array。forEach异步?

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

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

这将是非阻塞的吗?

346955 次浏览

不,它堵塞了。看看算法说明

然而,中数上给出的实现可能更容易理解:

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

这将是非阻塞的。这个例子来自高性能JavaScript

另一个选项可能是< em > web workers < / em >

Array.forEach是用于计算不等待的东西,并且在事件循环中使计算异步是没有任何收获的(如果你需要多核计算,网络工作者会添加多处理)。如果希望等待多个任务结束,可以使用计数器,可以将计数器包装在信号量类中。

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

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

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

例如:

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

如果你需要一个异步友好的Array.forEach或类似版本,它们可以在Node.js的async模块中使用:http://github.com/caolan/async…作为奖励,这个模块也可以在浏览器中工作。

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

这是一个不需要第三方库就可以使用的简短异步函数

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);
};
< p >编辑2018-10-11: 看起来下面描述的标准很有可能无法通过,考虑管道作为替代方案(行为不完全相同,但方法可以在类似的庄园中实现)

这就是为什么我对es7感到兴奋,在未来你将能够做一些像下面的代码(一些规格不完整,所以使用时要小心,我会尽量保持最新)。但是基本上使用新的::bind操作符,你将能够在对象上运行一个方法,就好像对象的原型包含了这个方法一样。例如[Object]::[Method],通常你会调用[Object].[ObjectsMethod]

注意今天(7月24日-16日)要做到这一点,并让它在所有浏览器中都能工作,你需要为以下功能编译代码:进口/出口箭头功能承诺异步/等待,最重要的是功能绑定。如果必要,下面的代码可以修改为只使用函数绑定,所有这些功能今天都可以使用巴别塔

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

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

还有另一个变体forAllAsync

甚至可以像这样编码解决方案,例如:

 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

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

[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

使用蓝知更鸟库的Promise.each

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

此方法迭代数组或数组的promise,其中包含带有签名(值,索引,长度)的给定迭代器函数的承诺(或承诺和值的混合),其中价值是输入数组中各自承诺的解析值。迭代是连续发生的。如果迭代器函数返回promise或thenable,则在继续下一次迭代之前等待promise的结果。如果输入数组中的任何承诺被拒绝,那么返回的承诺也会被拒绝。

如果所有迭代都成功解析,则Promise.each 解析为未修改的原始数组. c。然而,如果一次迭代被拒绝或出现错误,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");
});

虽然数组。如果不是异步的,你可以得到异步的“最终结果”。在下面的例子:

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

这些代码片段将使您更好地理解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()