Node.jsネイティブPromise.allは並列処理ですか、それとも順次処理ですか?


173

ドキュメントについてはあまり明確ではないので、この点を明確にしたいと思います。

Q1:ですPromise.all(iterable)順次または並列にすべての約束を処理しますか?または、より具体的には、次のようなチェーンされたpromiseを実行することと同等です。

p1.then(p2).then(p3).then(p4).then(p5)....

またはそれは、すべてのアルゴリズムのいくつかの他の一種であるp1p2p3p4p5、など(並列に)同じ時に呼び出されているとの結果がすべて解決(または1人の不合格品)とすぐに返されますか?

Q2:Promise.all並列実行する場合、反復可能オブジェクトを連続して実行する便利な方法はありますか?

:Q、またはBluebirdを使用したくありませんが、すべてのネイティブES6仕様を使用します。


ノード(V8)の実装、または仕様について質問していますか?
2015年

1
私はPromise.allそれらを並列で実行すると確信しています。
royhowie

@Amit私はフラグを立てましたnode.js、そしてio.jsこれが私がそれを使用しているところです。ですから、そうであれば、V8実装を使用します。
Yanick Rochon、2015年

9
約束は「実行される」ことができません。それらは作成されたときにタスクを開始します-それらは結果のみを表します -そして、それらを渡す前であっても、すべてを並行して実行していPromise.allます。
Bergi

約束は作成の瞬間に実行されます。(少しのコードを実行することで確認できます)。中new Promise(a).then(b); c();次いでC、B、最初に実行されます。これらのプロミスを実行するのはPromise.allではなく、解決時に処理するだけです。
Mateon1 2015年

回答:


257

されたPromise.all(iterable)すべての約束を実行しますか?

いいえ、約束は「実行」できません。それらは作成されたときにタスクを開始します-それらは結果のみを表します -そして、それらを渡す前であっても、すべてを並行して実行していPromise.allます。

Promise.all複数の約束を待つだけです。解決の順序や、計算が並列で実行されているかどうかは関係ありません。

イテラブルを順次実行する便利な方法はありますか?

あなたがすでに約束をしているなら、あなたは多くをすることはできませんがPromise.all([p1, p2, p3, …])(それはシーケンスの概念を持っていません)。ただし、反復可能な非同期関数がある場合は、実際にそれらを順次実行できます。基本的にあなたはから得る必要があります

[fn1, fn2, fn3, …]

fn1().then(fn2).then(fn3).then(…)

そしてそれを行うソリューションは次を使用していArray::reduceます:

iterable.reduce((p, fn) => p.then(fn), Promise.resolve())

1
この例では、呼び出し可能なpromiseを返す関数の配列が反復可能ですか?
James Reategui

2
@SSHThis:thenシーケンスとまったく同じです。戻り値は最後のfn結果の約束であり、他のコールバックをそれにチェーンすることができます。
Bergi、2016年

1
@wojjasこれはまさにfn1().then(p2).then(fn3).catch(…?関数式を使用する必要はありません。
Bergi、

1
@wojjasもちろん、retValFromF1はに渡されますがp2、それはまさにその通りp2です。もちろん、追加の変数を渡したり、複数の関数を呼び出したりする場合は、関数式を使用する必要がありますがp2、配列を変更する方が簡単です
Bergi

1
@ robe007はい、私はそれが意味iterableある[fn1, fn2, fn3, …]配列
Bergi

62

並行して

await Promise.all(items.map(async item => { await fetchItem(item) }))

利点:より高速。失敗した場合でも、すべての反復が実行されます。

順番通りに

for (let i = 0; i < items.length; i++) {
    await fetchItem(items[i])
}

利点:ループ内の変数は、各反復で共有できます。通常の命令同期コードのように動作します。


7
または:for (const item of items) await fetchItem(item);
Robert Penner

1
@david_adler並列の例の利点では、1つ失敗してもすべての反復が実行されると述べました。私が間違っていなければ、これはまだすぐに失敗します。:ような何かを行うことができ、この動作を変更するには、1 await Promise.all(items.map(async item => { return await fetchItem(item).catch(e => e) }))
Taimoor

@Taimoorはい、「高速に失敗」し、Promise.allの後もコードの実行を継続しますが、すべての反復は引き続き実行されますcodepen.io/mfbx9da4/pen/BbaaXr
david_adler

このアプローチは、async関数がAPI呼び出しであり、サーバーをDDOSにしたくない場合に適しています。実行時にスローされる個々の結果とエラーをより適切に制御できます。さらによいのは、どのエラーを続行し、何をループから外すかを決定できることです。
マンダリン

javascriptはシングルスレッドであるため、javascriptは実際にはスレッドを使用して「並列」で非同期要求を実行していないことに注意してください。developer.mozilla.org/en-US/docs/Web/JavaScript/EventLoop
david_adler

11

ベルギスの答えは、Array.reduceを使用して正しい軌道に乗った。

ただし、実際に関数が次々と実行するという約束を返すには、さらにネストを追加する必要がありました。

私の実際の使用例は、ダウンストリームの制限のため、次々と順番に転送する必要があるファイルの配列です...

これが私が最終的に得たものです。

getAllFiles().then( (files) => {
    return files.reduce((p, theFile) => {
        return p.then(() => {
            return transferFile(theFile); //function returns a promise
        });
    }, Promise.resolve()).then(()=>{
        console.log("All files transferred");
    });
}).catch((error)=>{
    console.log(error);
});

以前の答えが示唆するように、

getAllFiles().then( (files) => {
    return files.reduce((p, theFile) => {
        return p.then(transferFile(theFile));
    }, Promise.resolve()).then(()=>{
        console.log("All files transferred");
    });
}).catch((error)=>{
    console.log(error);
});

転送が完了するのを待たずに次の転送を開始しました。また、「すべてのファイルが転送されました」というテキストは、最初のファイル転送が開始される前でもありました。

私が何を間違えたかはわかりませんが、私にとって何がうまくいったかを共有したいと思いました。

編集:この投稿を書いてから、最初のバージョンが機能しない理由がわかりました。then()は、promiseを返す関数を想定しています。そのため、括弧なしで関数名を渡す必要があります!さて、私の関数は引数を必要としているので、引数を取らない匿名関数でラップする必要があります!


4

@Bergiの答えを詳しく説明するため(これは非常に簡潔ですが、理解するのが難しいです)。

このコードは、配列内の各項目を実行し、次の「その後のチェーン」を最後に追加します。

function eachorder(prev,order) {
        return prev.then(function() {
          return get_order(order)
            .then(check_order)
            .then(update_order);
        });
    }
orderArray.reduce(eachorder,Promise.resolve());

それが理にかなっていると思います。


3

再帰関数を使用して非同期関数で反復可能オブジェクトを順次処理することもできます。たとえばa、非同期関数で処理する配列を指定しますsomeAsyncFunction()

var a = [1, 2, 3, 4, 5, 6]

function someAsyncFunction(n) {
  return new Promise((resolve, reject) => {
    setTimeout(() => {
      console.log("someAsyncFunction: ", n)
      resolve(n)
    }, Math.random() * 1500)
  })
}

//You can run each array sequentially with: 

function sequential(arr, index = 0) {
  if (index >= arr.length) return Promise.resolve()
  return someAsyncFunction(arr[index])
    .then(r => {
      console.log("got value: ", r)
      return sequential(arr, index + 1)
    })
}

sequential(a).then(() => console.log("done"))


使用してarray.prototype.reduce再帰関数よりもパフォーマンスの面ではるかに優れている
はMateuszSowiński

@MateuszSowiński、各呼び出しの間に1500msのタイムアウトがあります。これが非同期呼び出しを順番に実行していることを考えると、非常に迅速な非同期のターンアラウンドでも、それがどのように関連しているかを理解するのは困難です。
マークマイヤー

40個の非常に高速な非同期関数を相互に実行する必要があるとしましょう-再帰関数を使用すると、メモリが非常に速く詰まります
MateuszSowińskiJul

@MateuszSowiński、スタックはここに巻き込まれません...各呼び出しの後に戻ります。チェーンreduce全体then()を1つのステップで構築してから実行する必要がある場合と比較してください。
マークマイヤー

シーケンシャル関数の40番目の呼び出しでは、関数の最初の呼び出しはメモリ内にあり、シーケンシャル関数のチェーンが戻るのを待っています
MateuszSowińskiJul

2

async awaitを使用すると、promiseの配列を簡単に順次実行できます。

let a = [promise1, promise2, promise3];

async function func() {
  for(let i=0; i<a.length; i++){
    await a[i]();
  }  
}

func();

注:上記の実装では、promiseが拒否された場合、残りは実行されません。すべてのpromiseを実行する場合は、await a[i]();内部をラップしてくださいtry catch


2

平行

この例を見る

const resolveAfterTimeout = async i => {
  return new Promise(resolve => {
    console.log("CALLED");
    setTimeout(() => {
      resolve("RESOLVED", i);
    }, 5000);
  });
};

const call = async () => {
  const res = await Promise.all([
    resolveAfterTimeout(1),
    resolveAfterTimeout(2),
    resolveAfterTimeout(3),
    resolveAfterTimeout(4),
    resolveAfterTimeout(5),
    resolveAfterTimeout(6)
  ]);
  console.log({ res });
};

call();

コードを実行することにより、6つの約束すべてに対して「CALLED」をコンソールし、それらが解決されると、タイムアウト後に6つの応答ごとに同時にコンソールします。


2

NodeJSはプロミスを並行して実行するのではなく、シングルスレッドのイベントループアーキテクチャであるため、同時に実行します。マルチコアCPUを利用するために新しい子プロセスを作成することで、物事を並行して実行する可能性があります。

並行対並行

実際、何 Promise.allいうと、promise関数を適切なキューにスタックし(イベントループアーキテクチャを参照)、同時に実行し(P1、P2などを呼び出します)、各結果を待ってから、Promise.allをすべてのpromiseで解決します。結果。Promise.allは、拒否を自分で管理しない限り、失敗した最初の約束で失敗します。

並列処理と並行処理には大きな違いがあります。最初のものは別々のプロセスで異なる計算をまったく同時に実行し、それらはそこにあるリズムで進行しますが、もう1つは前のものを待たずに次々と異なる計算を実行します相互に依存することなく同時に完了して進行する計算。

最後に、あなたの質問に答えるPromise.allために、並列または順次ではなく、同時に実行します。


これは正しくない。NodeJSは物事を並行して実行できます。NodeJSにはワーカースレッドの概念があります。デフォルトでは、ワーカースレッドの数は4です。たとえば、暗号ライブラリを使用して2つの値をハッシュすると、それらを並行して実行できます。2つのワーカースレッドがタスクを処理します。もちろん、並列処理をサポートするには、CPUがマルチコアである必要があります。
Shihab

ええ、そうです、それは私が最初の段落の終わりで言ったことですが、私は子プロセスについて話しました、もちろん彼らは労働者を動かすことができます。
エイドリアンデペレッティ

1

Bergiの答えは、呼び出しを同期化するのに役立ちました。前の関数が呼び出された後に各関数を呼び出す例を以下に追加しました。

function func1 (param1) {
    console.log("function1 : " + param1);
}
function func2 () {
    console.log("function2");
}
function func3 (param2, param3) {
    console.log("function3 : " + param2 + ", " + param3);
}

function func4 (param4) {
    console.log("function4 : " + param4);
}
param4 = "Kate";

//adding 3 functions to array

a=[
    ()=>func1("Hi"),
    ()=>func2(),
    ()=>func3("Lindsay",param4)
  ];

//adding 4th function

a.push(()=>func4("dad"));

//below does func1().then(func2).then(func3).then(func4)

a.reduce((p, fn) => p.then(fn), Promise.resolve());

これは元の質問に対する答えですか?
Giulio Caccin

0

forループでできます。

非同期関数が約束を返す

async function createClient(client) {
    return await Client.create(client);
}

let clients = [client1, client2, client3];

次のコードを書くと、クライアントは並行して作成されます

const createdClientsArray = yield Promise.all(clients.map((client) =>
    createClient(client);
));

その後、すべてのクライアントが並行して作成されます。クライアントを順番に作成したい場合は、forループを使用する必要があります

const createdClientsArray = [];
for(let i = 0; i < clients.length; i++) {
    const createdClient = yield createClient(clients[i]);
    createdClientsArray.push(createdClient);
}

その後、すべてのクライアントが順番に作成されます。

幸せなコーディング:)


8
現時点では、async/ awaitはトランスパイラーでのみ、またはノード以外のエンジンを使用してのみ使用できます。また、実際にと混同asyncしないでくださいyield。トランスパイラーとで同じように動作しcoますが、実際にはかなり異なり、通常は互いに従うべきではありません。また、あなたの答えは初心者プログラマーを混乱させるので、これらの制限に言及する必要があります。
Yanick Rochon、2016

0

私は連続した約束を解決するためにforを使ってきました。それがここで役立つかどうかはわかりませんが、これは私がやってきたことです。

async function run() {
    for (let val of arr) {
        const res = await someQuery(val)
        console.log(val)
    }
}

run().then().catch()

0

これはあなたの質問の一部に答えるかもしれません。

はい、次のように関数を返すpromiseの配列をチェーンできます...(これにより、各関数の結果が次の関数に渡されます)。もちろん、それを編集して、各関数に同じ引数(または引数なし)を渡すこともできます。

function tester1(a) {
  return new Promise(function(done) {
    setTimeout(function() {
      done(a + 1);
    }, 1000);
  })
}

function tester2(a) {
  return new Promise(function(done) {
    setTimeout(function() {
      done(a * 5);
    }, 1000);
  })
}

function promise_chain(args, list, results) {

  return new Promise(function(done, errs) {
    var fn = list.shift();
    if (results === undefined) results = [];
    if (typeof fn === 'function') {
      fn(args).then(function(result) {
        results.push(result);
        console.log(result);
        promise_chain(result, list, results).then(done);
      }, errs);
    } else {
      done(results);
    }

  });

}

promise_chain(0, [tester1, tester2, tester1, tester2, tester2]).then(console.log.bind(console), console.error.bind(console));


0

NodeJSの問題、つまりファイルチャンクの再構成を解決しようとしているときに、このページを偶然見つけました。基本的に、私はファイル名の配列を持っています。1つの大きなファイルを作成するには、これらのファイルをすべて正しい順序で追加する必要があります。これは非同期で行う必要があります。

ノードの「fs」モジュールはappendFileSyncを提供しますが、この操作中にサーバーをブロックしたくありませんでした。私はfs.promisesモジュールを使用して、これらを一緒にチェーンする方法を見つけたかったのです。このページの例は、ファイルチャンクを読み取るfsPromises.read()と宛先ファイルに連結するfsPromises.appendFile()という2つの操作が実際に必要だったので、私にとってはうまくいきませんでした。もしかしたら、JavaScriptの方がよかったのであれば、以前の答えをうまく機能させることができたでしょう。;-)

私はこれを偶然見つけました... https://css-tricks.com/why-using-reduce-to-sequentially-resolve-promises-works/ ...そして私は作業中のソリューションを一緒にハッキングすることができました。

TLDR:

/**
 * sequentially append a list of files into a specified destination file
 */
exports.append_files = function (destinationFile, arrayOfFilenames) {
    return arrayOfFilenames.reduce((previousPromise, currentFile) => {
        return previousPromise.then(() => {
            return fsPromises.readFile(currentFile).then(fileContents => {
                return fsPromises.appendFile(destinationFile, fileContents);
            });
        });
    }, Promise.resolve());
};

そして、これはジャスミンの単体テストです:

const fsPromises = require('fs').promises;
const fsUtils = require( ... );
const TEMPDIR = 'temp';

describe("test append_files", function() {
    it('append_files should work', async function(done) {
        try {
            // setup: create some files
            await fsPromises.mkdir(TEMPDIR);
            await fsPromises.writeFile(path.join(TEMPDIR, '1'), 'one');
            await fsPromises.writeFile(path.join(TEMPDIR, '2'), 'two');
            await fsPromises.writeFile(path.join(TEMPDIR, '3'), 'three');
            await fsPromises.writeFile(path.join(TEMPDIR, '4'), 'four');
            await fsPromises.writeFile(path.join(TEMPDIR, '5'), 'five');

            const filenameArray = [];
            for (var i=1; i < 6; i++) {
                filenameArray.push(path.join(TEMPDIR, i.toString()));
            }

            const DESTFILE = path.join(TEMPDIR, 'final');
            await fsUtils.append_files(DESTFILE, filenameArray);

            // confirm "final" file exists    
            const fsStat = await fsPromises.stat(DESTFILE);
            expect(fsStat.isFile()).toBeTruthy();

            // confirm content of the "final" file
            const expectedContent = new Buffer('onetwothreefourfive', 'utf8');
            var fileContents = await fsPromises.readFile(DESTFILE);
            expect(fileContents).toEqual(expectedContent);

            done();
        }
        catch (err) {
            fail(err);
        }
        finally {
        }
    });
});

誰かのお役に立てば幸いです。

弊社のサイトを使用することにより、あなたは弊社のクッキーポリシーおよびプライバシーポリシーを読み、理解したものとみなされます。
Licensed under cc by-sa 3.0 with attribution required.