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


回答:


392

いいえ、ブロックしています。アルゴリズム仕様をご覧ください

ただし、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ワーカーです。


37
Node.jsを使用している場合は、setTimeoutの代わりにprocess.nextTickの使用も検討してください
Marcello Bastea-Forte

28
技術的には、CPUがスリープ状態になることはないため、forEachは「ブロッキング」ではありません。これは同期的でCPUバウンドであり、ノードアプリがイベントに応答することを期待すると、「ブロック」のように感じることがあります。
Dave Dopson、2011

3
ここではおそらく非同期の方がより適切なソリューションです(実際、誰かがそれを回答として投稿したのを見ただけです!)
ジェームス

6
私はこの答えを信頼しましたが、場合によっては間違っているようです。たとえば、ステートメントをブロックforEachawaitforループを使用する必要があります。stackoverflow.com
Richard

3
@リチャード:もちろん。await内部async関数のみを使用できます。しかしforEach、非同期関数が何であるかはわかりません。非同期関数は単にpromiseを返す関数であることに注意してください。あなたは期待するforEachコールバックから返された約束を処理するために?forEachコールバックからの戻り値を完全に無視します。それ自体が非同期の場合のみ、非同期コールバックを処理できます。
Felix Kling 2016年

80

の非同期対応バージョンなどが必要な場合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
});

2
非同期操作が(コレクションの順序で)一度に1つの項目に対してのみ実行されるようにする必要がある場合は、eachSeries代わりにを使用する必要があります。
matpop

@JohnKennedy私は前に会ったことがあります!
Xsmael

16

Nodeで非常に重い計算を行うための一般的なパターンがあり、それはあなたに適用できるかもしれません...

ノードはシングルスレッドです(設計上の意図的な選択として、Node.jsとはを参照してください)。つまり、単一のコアのみを利用できます。最近のボックスには8、16、またはそれ以上のコアがあるため、マシンの90%以上がアイドル状態になる可能性があります。RESTサービスの一般的なパターンは、コアごとに1つのノードプロセスを起動し、これらを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();
}

11
明確にするために...ノードはシングルスレッドではありませんが、JavaScriptの実行はそうです。IOと別のスレッドで実行されないもの。
ブラッド

3
@ブラッド-多分。それは実装に依存します。適切なカーネルサポートがあれば、ノードとカーネルの間のインターフェースは、イベントベース(kqueue(mac)、epoll(linux)、IO完了ポート(windows))にすることができます。フォールバックとして、スレッドのプールも機能します。あなたの基本的な点は正しいです。低レベルのNode実装には複数のスレッドがある場合があります。ただし、言語モデル全体が壊れるので、JSユーザーランドに直接公開することはありません。
Dave Dopson、2013年

4
正解です。コンセプトが多くを混乱させているので、私は明確にしています。
ブラッド

6

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


5

2018-10-11を編集:以下で説明する標準が適用されない可能性が高いようです。パイプラインを代替として検討してください(まったく同じようには動作しませんが、メソッドは同様の方法で実装できます)。

これがまさに私がes7に興奮している理由です。将来、以下のコードのようなことができるようになります(一部の仕様は完全ではないので、注意して使用してください。これを最新に保つようにします)。ただし、基本的には、新しい::バインド演算子を使用すると、オブジェクトのプロトタイプにメソッドが含まれている場合と同じように、オブジェクトに対してメソッドを実行できます。例:[Object] :: [Method]通常は[Object]。[ObjectsMethod]を呼び出します

:今日(24 7月-16)この操作を行うと、それはあなたが次の機能のためにあなたのコードをtranspileする必要がありますすべてのブラウザで動作持っているノートのインポート/エクスポートアロー機能約束非同期/待って、最も重要な機能のバインド。以下のコードは、必要に応じて関数バインドのみを使用するように変更することができます。この機能はすべて、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;
    });
};

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

これは非同期ですか?AFAIK #callはすぐに実行されますか?
Giles Williams

1
もちろんすぐですが、すべての反復がいつ完了したかを知るためのコールバック関数があります。ここで「イテレーター」引数は、コールバックを備えたノードスタイルの非同期関数です。これはasync.eachメソッドに似ています
Rax Wunter '29

3
これが非同期であることがわかりません。呼び出しまたは適用は同期的です。コールバックがあっても非同期にはなりません
adrianvlupu

JavaScriptでは、人々が非同期と言うとき、それらはコードの実行がメインイベントループをブロックしないことを意味します(つまり、プロセスがコードの1行で動かなくなることはありません)。コールバックを配置するだけでコードが非同期になるわけではなく、setTimeoutやsetIntervalなどのイベントループ解放の形式を利用する必要があります。あなたがそれらを待つ時間を過ごしているので、他のコードは中断することなく実行できます。
vasilevich

0

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のもう 1つのバリエーション


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」よりもはるかに低速です。

それ以外の場合、優れたAsyncライブラリはこれを行うことができます:https : //caolan.github.io/async/docs.html#each


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

これは、async.foreachまたはその他の並列メソッドを記述した場合でも発生します。forループはIOプロセスではないため、Nodejsは常に同期的に実行します。
Sudhanshu Gaur

-2

使用Promise.eachブルーバードライブラリ。

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

このメソッドは、配列、または配列のpromiseを反復処理します。これには、値がaの解決された値であるシグネチャ(値、インデックス、長さ)を指定された反復関数を持つpromise(またはpromiseとvalueの混合が含まれます入力配列のそれぞれの約束。反復は連続的に行われます。反復関数がpromiseまたはthenableを返す場合、次の反復を続行する前にpromiseの結果が待機されます。入力配列内のpromiseが拒否された場合、返されたpromiseも拒否されます。

すべての反復が正常に解決されると、Promise.each は変更されていない元の配列に解決されます。ただし、1つの反復で拒否またはエラーが発生した場合、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");
});
弊社のサイトを使用することにより、あなたは弊社のクッキーポリシーおよびプライバシーポリシーを読み、理解したものとみなされます。
Licensed under cc by-sa 3.0 with attribution required.