我对JavaScript的本地Array.forEach
实现有疑问:它是否异步?
例如,如果我打电话:
[many many elements].forEach(function () {lots of work to do})
这会不会阻塞吗?
答案 0 :(得分:355)
不,它是封锁的。看看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。
答案 1 :(得分:72)
如果你需要一个异步友好版本的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
});
答案 2 :(得分:15)
在Node中进行非常繁重的计算有一种常见的模式可能适用于你......
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(); }
答案 3 :(得分:4)
Array.forEach
用于计算不等待的东西,并且没有任何东西可以使计算在事件循环中异步(webworkers添加多处理,如果你需要多核计算)。如果要等待多个任务结束,请使用计数器,您可以将其包装在信号量类中。
答案 4 :(得分:4)
编辑2018-10-11: 看起来很有可能下面描述的标准可能不会通过,考虑pipelineing作为替代方案(行为不完全相同,但方法可以在类似的庄园中实施)。
这正是我对es7感到兴奋的原因,将来你可以做类似下面代码的事情(有些规格不完整所以请谨慎使用,我会尽量保持最新)。但基本上使用new :: bind运算符,您将能够在对象上运行一个方法,就像对象的原型包含该方法一样。例如[Object] :: [Method]通常你会调用[Object]。[ObjectsMethod]
请注意今天(6月24日至16日)执行此操作并使其适用于所有浏览器,您需要为以下功能转换代码:导入/导出,箭头功能,承诺,异步/等待,最重要的是功能绑定。下面的代码可以修改为仅使用函数绑定,如果有必要,所有这些功能今天都可以使用babel整齐地获得。
YourCode.js(其中'要做的很多工作'必须简单地返回一个promise,在异步工作完成后解析它。)
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;
});
};
答案 5 :(得分: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);
};
答案 6 :(得分:0)
npm上有一个方便的forAllAsync。
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');
});
另一种变体find()
答案 7 :(得分: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);
});
另一方面,它比#34;慢得多。
否则,出色的异步库可以执行此操作:https://caolan.github.io/async/docs.html#each
答案 8 :(得分: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
答案 9 :(得分:0)
它不是异步的。它正在阻塞。那些在尝试使用JS之前先学习过Java,C或Python之类的语言的人,在尝试将任意延迟或API调用放入循环体时会感到困惑。
说您尝试这个:
const array = [1, 2, 3, 4, 5];
array.forEach((el, i) => {
setTimeout(() => {
console.log(el);
}, 1000);
});
代码按顺序执行,并且下一个循环迭代直到上一个循环通过最后一行才开始执行。
您可能希望setTimeout能够保持执行,直到延迟时间过去。事实并非如此,因为setTimeout方法实际上立即返回一个值。在Node.js中,它返回一个Timeout对象,在浏览器中,它返回一个指向超时实例的数字。
返回某些内容后,下一行开始执行。这样一来,您最终会在程序执行超过1秒的时间内得到这种效果,并且在关闭之前,它几乎同时吐出5个console.logs。
如果您希望超时来强制循环等待直到其延迟过去,则可以使用以下代码。
function forEachWithCallback(callback) {
const arrayCopy = this;
let index = 0;
const next = () => {
index++;
if (arrayCopy.length > 0) {
callback(arrayCopy.shift(), index, next);
}
}
next();
}
Array.prototype.forEachWithCallback = forEachWithCallback;
const array = [1, 2, 3, 4, 5];
array.forEachWithCallback((el, i, next) => {
setTimeout(() => {
console.log(el);
next();
}, 1000);
});
您可以使用回调函数添加自己的方法,而不是使用JS数组随附的本机forEach方法。使用此方法将使您的迭代等待其优先级的超时完成。下一次迭代要等到循环主体中调用“ next”方法后才能开始。因此,这段代码只需花费5秒钟以上的时间执行,并且每秒只有一个console.log。
这同样适用于JS Promises,例如当您想要做Ajax时。
// "request" implementation can be found in the link at the bottom of this answer
const array = [1, 2, 3, 4, 5];
array.forEachWithPromise((el, i, next) => {
request({
method: 'GET',
hostname: 'httpbin.org',
path: '/get?myArg=' + el
}).then((res) => {
const responseBody = JSON.parse(res.body);
console.log(responseBody.args.myArg);
next();
}).error((err) => {
console.error(err);
});
});
这里有很多免费代码,您可以在浏览器和Node.js中使用它们进行for-each循环,而无需外部程序包或依赖项。 https://gist.github.com/ajb413/d55489eec64db0bb4079a8d7af733aab
答案 10 :(得分:0)
虽然 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));
});
答案 11 :(得分:0)
这些代码片段会让你更好地理解 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()
答案 12 :(得分:-1)
使用bluebird库的 Promise.each 。
Promise.each(
Iterable<any>|Promise<Iterable<any>> input,
function(any item, int index, int length) iterator
) -> Promise
此方法遍历包含给定 iterator 函数和签名(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");
});