web-dev-qa-db-ja.com

Node.jsネイティブのPromise.allは並行して処理されますか、それとも順次処理されますか?

documentation はあまり明確ではないので、この点を明確にしたいと思います。

Q1:Promise.all(iterable)は、すべてのプロミスを順次または並列で処理していますか?または、より具体的には、次のような連鎖プロミスの実行と同等ですか?

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

または、p1p2p3p4p5などが同時に(並行して)呼び出され、すべてが解決(または1つの拒否)されるとすぐに結果が返される、他の種類のアルゴリズムですか?

Q2:Promise.allが並行して実行される場合、反復可能なシーケンシャルを実行する便利な方法はありますか?

:QやBluebirdは使いたくないが、すべてのネイティブES6仕様を使いたい。

130
Yanick Rochon

Promise.all(iterable)はすべてのプロミスを実行していますか?

いいえ、約束は「実行」できません。 createdであるときにタスクを開始します-結果のみを表します-andyouPromise.allに渡す前でも、すべてを並列に実行しています。

Promise.allは、複数のプロミスawaitのみを行います。解決する順序や、計算が並行して実行されているかどうかは関係ありません。

反復可能なシーケンシャルを実行する便利な方法はありますか?

すでに約束がある場合は、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())
204
Bergi

並行して

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

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

順番通りに

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

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

39
david_adler

Bergisの答えは、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()はfunctionがpromiseを返すことを期待しています。したがって、括弧なしで関数名を渡す必要があります!今、私の関数は引数を必要としているので、引数を取らない匿名関数でラップする必要があります!

9
tkarls

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

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"))
3
Mark Meyer

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

このコードは、配列内の各アイテムを実行し、次の「then chain」を最後に追加します。

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

それが理にかなっていることを願っています。

3
TimoSolo

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

はい、次のように関数を返すプロミスの配列を連鎖させることができます...(これは各関数の結果を次へ渡します)。もちろん、同じ関数を引数に渡して(または引数を渡さずに)各関数に渡すように編集することもできます。

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
cestmoi

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

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

run().then().catch()
0
Nick Kotenberg

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でラップします

0
Ayan

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

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

ハッピーコーディング:)

0
Deepak Sisodiya

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());
0
Nithi