我对 JavaScript 的本机 Array.forEach
实现有疑问:它的行为是否异步?例如,如果我打电话:
[many many elements].forEach(function () {lots of work to do})
这将是非阻塞的吗?
不,它正在阻塞。看看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。
如果您需要一个异步友好版本的 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
。
在 Node 中进行非常繁重的计算有一个常见的模式,它可能适用于你......
节点是单线程的(作为一个深思熟虑的设计选择,请参阅 What is Node.js?);这意味着它只能使用一个内核。现代机器有 8 个、16 个甚至更多内核,因此这可能会使 90% 以上的机器处于空闲状态。 REST 服务的常见模式是为每个核心启动一个节点进程,并将这些进程置于本地负载均衡器(如 http://nginx.org/)之后。
分叉一个孩子 - 对于你想要做的事情,还有另一种常见的模式,分叉一个子进程来完成繁重的工作。好处是子进程可以在后台进行大量计算,而您的父进程响应其他事件。问题是你不能/不应该与这个子进程共享内存(不是没有很多扭曲和一些本机代码);你必须传递消息。如果您的输入和输出数据的大小与必须执行的计算相比较小,这将非常有效。您甚至可以启动一个子 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
用于计算不等待的东西,并且在事件循环中使计算异步没有任何好处(如果您需要多核计算,webworkers 添加多处理)。如果您想等待多个任务结束,请使用计数器,您可以将其包装在信号量类中。
编辑 2018-10-11:看起来下面描述的标准很有可能无法通过,请考虑将 pipelineing 作为替代方案(行为不完全相同,但可以在类似的庄园中实施方法)。
这正是我对 es7 感到兴奋的原因,将来您将能够执行以下代码之类的操作(某些规范不完整,因此请谨慎使用,我会尽量保持最新)。但基本上使用 new :: bind 运算符,您将能够在对象上运行方法,就好像对象的原型包含该方法一样。例如 [Object]::[Method] 通常你会调用 [Object].[ObjectsMethod]
请注意今天(2016 年 7 月 24 日)执行此操作并让它在所有浏览器中运行,您需要为以下功能转换代码:导入/导出、箭头函数、Promises、Async / 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;
});
};
这是一个简短的异步函数,无需第三方库即可使用
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);
};
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
甚至可以编写这样的解决方案,例如:
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
尽管 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)); });
这些代码片段将使您更好地理解 forEach 和 forOf 的比较。
/* eslint-disable no-console */ async function forEachTest() { console.log('########### Testing forEach ############### ') console.log('forEachTest 函数开始') let a = [1, 2, 3] await a.forEach(async (v) => { console.log('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)) //以正确的方式查看控制台 console.log('\n\n########### Testing forOf ###### ########## ') console.log('forOfTest 函数的开始') let a = [1, 2, 3] for (const v of a) { console.log('forOf 的开始: ', v) await new Promise(resolve => setTimeout(resolve, v * 1000)) console.log('end of forOf: ', v) } console.log('end of forOfTest func') } forOfTest()
这是一个小例子,你可以运行它来测试它:
[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
使用 bluebird 库的 Promise.each。
Promise.each(
Iterable<any>|Promise<Iterable<any>> input,
function(any item, int index, int length) iterator
) -> Promise
这个方法迭代一个数组,或者一个数组的promise,它包含promise(或者promise和values的混合)和给定的带有签名(value,index,length)的迭代器函数,其中value是a的resolved值输入数组中的相应承诺。迭代是连续发生的。如果迭代器函数返回一个 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");
});
forEach
not 会阻塞await
语句,您应该使用for
循环:stackoverflow.com/questions/37962880/…async
函数中使用await
。但是forEach
不知道什么是异步函数。请记住,异步函数只是返回承诺的函数。您是否希望forEach
处理从回调返回的承诺?forEach
完全忽略回调的返回值。如果它本身是异步的,它只能处理异步回调。