web-dev-qa-db-ja.com

JavaScript Promise.allには、成功と失敗があったときに発生するコールバックがありますか?

私はPromise.allを誤解していますか?配列にXのpromiseがあり、配列の成功/失敗の比率を集計しようとしています。

これが私が知っていると思うことです:

Promise.allは一連のpromiseを取ります。

すべてのpromiseが成功すると、.thenコールバックが実行されます。

約束の1つが失敗した場合、.catchコールバックが呼び出され、渡された引数は、発生した単一のエラーの値です。

一部が成功し、一部が失敗した場合のすべての約束の結果であるコールバックは発生しません。つまり(擬似コード)[success, fail, success, success]のような配列を提供することはできません-予想されるように、多くのJSライブラリ(ajax、emberなど)で見つけることができます。

.then.successに似ており、成功したか失敗したかに関係なく、すべての約束が満たされた後に常に実行される関数ではありません。なぜ.when.finally.runThisShizNoMatterWhatがないのですか?または私は何かが欠けていますか(非常に可能性が高い)?

12
Taysky

これは Bluebird Promise.all-成功と拒否を集約して完了した複数のプロミス に関連していますが、これはBluebird固有です。問題の核心は、何かが成功したか失敗したかを調べたい場合、各約束の直接の結果を実際に求めているのではないということです。代わりに、Promise.allを使用する前にpromiseを変換する必要があります。このES6標準の約束にはヘルパーはありませんが、実装するのは簡単です。ほとんどのライブラリでは、これはPromise.settleとして知られています。例えば

var someThings = [...]; // some list of promises that may succeed or fail
settle(someThings).then(results => {
  results.forEach(result => {
    if (result.state === 'fullfilled'){
      console.log('succeeded', result.value);
    } else {
      console.log('failed', result.value);
    }
  });
});


function settle(arr){
  return Promise.all(arr.map(promise => {
    return promise.then(
      value => ({state: 'fullfilled', value}),
      value => ({state: 'rejected', value})
    );
  }));
}
14
loganfsmyth

値とは別にエラーをうまく伝えている場合は、次のように簡単に実行できます。

Promise.all(array.map(promise => promise.catch(error => error)))
var log = msg => div.innerHTML += "<p>" + msg + "</p>";

var a = () => Promise.resolve(1);
var b = () => Promise.reject("error");
var c = () => Promise.resolve(3);

Promise.all([a(), b(), c()].map(p => p.catch(e => e))).then(r => log(r));
<div id="div"></div>
3
jib

約束の1つが拒​​否された場合、Promise.allによって返された約束は拒否されます。したがって、拒否ハンドラーは、promise拒否の1つの直後に呼び出されます。拒否を心配せずにすべてのPromiseを実行したいだけの場合(つまり、Promiseが拒否された場合は、Promiseを拒否しないでください)、これは望ましい動作ではない可能性があります。

個々のプロミスの拒否を処理できるため、拒否後に実行されます。

var promiseRejected = new Promise(function(resolve, reject){
  setTimeout(function(){
    reject('I was rejected');
    }, 1000);
  });

promiseRejected = promiseRejected.then(null, function(reason){
  //I was rejected, now lets fullfill it.
  return reason;
  });

var promiseResolved = new Promise(function(resolve, reject){
  setTimeout(function(){
    resolve('All good');
    }, 1500);
  });

var time = performance.now();

Promise.all([promiseRejected, promiseResolved]).then(function(results){
  //both promises fulfilled; 1500 msecs passed
  console.log(results[0], results[1], performance.now() - time);
  });

すべてのpromiseが解決/拒否されたときに解決するpromiseコンストラクター例:

Promise.when = function (arrPromises) {
    if (!Array.isArray(arrPromises)) {
        return new TypeError('Expecting an Array of Promises');
    }
    return new Promise(function (resolve, reject) {
        var len = arrPromises.length,
            values = [],
            settled = 0;

        function settle(value, index) {
            values[index] = value;
            settled++;
            if (len === settled) {
                resolve(values);
            }
        }
        if (len === 0) {
            resolve([]);
        } else {
            arrPromises.forEach(function (promise, index) {
                var handler = function (value) {
                    settle(value, index);
                };
                promise.then(handler, handler);
            });
        }
    });
}
3
MinusFour

_Promise.all_は、全体として解決または拒否することしかできない新しい約束を作成します。おそらく、最初の要素が述語と一致しない場合にfalseで返されるevery配列メソッドのセマンティクスを持っていると考えることができます。

then関数は最大2つの引数を取り、2番目は拒否されたハンドラーです。この意味で、それは単なるsuccess以上のものであり、実際にはすべてのケースを処理できます。 catchは、.then(undefined, function(reason) { ... })の略である便利なメソッドにすぎません。

Promise APIには必要なものがありません。恐れ入りますが、自分で実装する必要があります。

2
kraf

あなたの質問から、あなたはすべての約束を解決することを期待しているようですが、メソッド_promise.all_は保証せず、メソッド_promise.settle_だけが配列内の各約束を解決することを保証します。

_promise.all_の結果が必要で、各プロミスを解決し、さらにどのプロミスが解決または拒否されたかを通知する場合は、メソッド spex.batch がまさに必要です。

バッチ処理 からコピーされた例:

_var spex = require('spex')(Promise);

// function that returns a promise;
function getWord() {
    return Promise.resolve("World");
}

// function that returns a value;
function getExcl() {
    return '!';
}

// function that returns another function;
function nested() {
    return getExcl;
}

var values = [
    123,
    "Hello",
    getWord,
    Promise.resolve(nested)
];

spex.batch(values)
    .then(function (data) {
        console.log("DATA:", data);
    }, function (reason) {
        console.log("REASON:", reason);
    });
_

この出力:

_DATA: [ 123, 'Hello', 'World', '!' ]
_

getWordを次のように変更して、失敗させましょう。

_function getWord() {
    return Promise.reject("World");
}
_

これで、出力は次のようになります。

_REASON: [ { success: true, result: 123 },
  { success: true, result: 'Hello' },
  { success: false, result: 'World' },
  { success: true, result: '!' } ]
_

つまり、配列全体が解決され、インデックスにバインドされた結果が報告されます。

そして、理由全体を報告する代わりに、getErrors()を呼び出す場合:

_console.log("REASON:", reason.getErrors());
_

その場合、出力は次のようになります。

_REASON: [ 'World' ]
_

これは、発生したエラーのリストへの迅速なアクセスを簡素化するためだけのものです。

そして、 メソッドのプロトコル から、オプションのcb --callbackパラメーターを渡すことができ、どのプロミスが解決され、どのプロミスが拒否されたかがわかります。

1
vitaly-t

Bluebirdを使用している場合は、Bluebird.reflectを使用してsettleを実装することが最善の方法であることに同意します。ユースケースによっては良いかもしれない別の解決策があります。それはやや興味深い状況で私のために働いた。これは、Bluebirdをpromiseライブラリとしても想定しています。

私の場合、すべてPromise.methodでラップされた関数(「タスク」)の配列があります。これらのタスクは、拒否されたプロミスを返す場合や、プロミスの解決策となるプレーンな値を返すかスローする場合があります。これらすべてを実行し、すべての結果を収集する必要がありました(最初の失敗だけでなく)。

繰り返しになりますが、tasks配列の各項目はPromise.methodラッパー関数であることに注意してください。 (実装は簡単です。 http://bluebirdjs.com/docs/api/promise.method.html を参照してください)

var runTasks = function(tasks) {
     return Bluebird.reduce(tasks, function (results, task) {
          return task()
            .then(function (result) {
              results.success.Push(result)
              return results
            })
            .caught(function (result) {
              results.fail.Push(result)
              return results
            })
     }, { success: [], fail: [] })
}

次に、このように呼び出して、満たされた値の配列と拒否された値の配列を持つオブジェクトを取得します。

runTasks(taskArray)
  .then(function(results){
    // do whatever you want here
    // results.success is an array of the returned/resolved values
    // results.fail is an array of the rejected/thrown values
  })
0
granmoe