web-dev-qa-db-ja.com

JavaScript、Node.js:Array.forEachは非同期ですか?

私はJavaScriptのネイティブのArray.forEach実装に関して質問があります:それは非同期的に振る舞いますか?たとえば、私が呼び出すと:

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

これはノンブロッキングですか?

334
R. Gr.

いいえ、それはブロックされています。アルゴリズムの 仕様 を見てください。

ただし、理解しやすい実装が 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});

その場合、これはノンブロッキングです。例は、 高性能JavaScript から取得されます。

別のオプションは、 Webワーカー です。

359
Felix Kling

あなたが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
});
72
Caolan

あなたに適用できるかもしれないNodeで本当に重い計算をするための一般的なパターンがあります...

ノードはシングルスレッドです(意図的な設計選択として、 Node.jsとは何ですか? を参照)。つまり、シングルコアしか利用できないということです。最近のボックスには8、16、またはそれ以上のコアがあるため、これによりマシンの90%以上がアイドル状態になる可能性があります。 RESTサービスの一般的なパターンは、コアごとに1つのノードプロセスを起動し、これらを http://nginx.org//のようなローカルロードバランサーの後ろに置くことです

子をフォークする - あなたがやろうとしていることのために、別の一般的なパターンがあります。 。その利点は、親プロセスが他のイベントに応答している間、子プロセスはバックグラウンドで大量の計算を実行できることです。キャッチは、この子プロセスとメモリを共有することができない、またはするべきではないということです(たくさんのゆがみといくつかのネイティブコードがなければ)。メッセージを渡す必要があります。入力データと出力データのサイズが、実行しなければならない計算と比較して小さい場合、これは美しく機能します。子node.jsプロセスを起動して、以前使用していたのと同じコードを使用することもできます。

例えば:

 var child_process = require( 'child_process'); 
関数run_in_child(array、cb){
 var process = child_process.exec( 'ノードlibfn.js'、function(err) 、stdout、stderr){
 var output = JSON.parse(stdout); 
 cb(err、output); 
}); 
 process.stdin。 write(JSON.stringify(array)、 'utf8'); 
 process.stdin.end(); 
} 
15
Dave Dopson

Array.forEachは、待っていないものを計算するためのものであり、イベントループで計算を非同期にすることで得られるものはありません(マルチコア計算が必要な場合は、Webワーカーはマルチプロセッシングを追加します)。複数のタスクが終了するのを待ちたい場合は、カウンターを使用してください。これをセマフォー・クラスでラップすることができます。

4
Tobu

編集2018-10-11:下記の規格が通らない可能性が高いようですが、代わりに パイプライン処理 を検討してください(していません)まったく同じように振舞いますが、メソッドは同様の方法で実装できます。

これがまさに私がes7に興奮している理由です、将来あなたは以下のコードのようなことができるようになるでしょう(スペックのいくつかは完全ではないので注意して使ってください、私はこれを最新に保ちます)。しかし基本的にnew :: bind演算子を使用すると、あたかもオブジェクトのプロトタイプにそのメソッドが含まれているかのように、オブジェクトに対してメソッドを実行することができます。例[Object] :: [Method]通常は[Object]と呼びます。[ObjectsMethod]

今日(7月24日 - 16日)これを行い、次の機能のためにコードを変換する必要があるすべてのブラウザで動作するようにしてください。Import/Export矢印関数約束非同期/待機、そして最も重要なのは )関数バインド。以下のコードは、必要ならば関数バインドのみを使用するように修正できます。 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;
    });
};
4
Josh Mc

これは、サードパーティのライブラリを必要とせずに使用するための短い非同期関数です。

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);
};
1
Rax Wunter

これをテストするために実行できる小さな例です。

[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
0
adiian

各ループに対して非同期 非同期の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"よりずっと遅いです。

さもなければ、優秀な非同期ライブラリはこれをすることができます: https://caolan.github.io/async/docs.html#each

0
signo