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

时间:2011-02-19 10:36:48

标签: javascript arrays asynchronous foreach node.js

我对JavaScript的本地Array.forEach实现有疑问:它是否异步? 例如,如果我打电话:

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

这会不会阻塞吗?

13 个答案:

答案 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");
});