【问题标题】:Does JavaScript Promise.all have a callback that is fired when there are success AND failures [duplicate]JavaScript Promise.all 是否有在成功和失败时触发的回调 [重复]
【发布时间】:2016-01-03 21:28:02
【问题描述】:

我误解了 Promise.all 吗?我在一个数组中有 X 个 promise,我正在尝试汇总数组的成功/失败比率。

以下是我认为我知道的:

Promise.all 接受一组承诺。

如果所有的承诺都成功了,那么.then 回调就会运行。

如果其中一个承诺失败,则调用 .catch 回调,传入的参数是单个引发错误的值。

如果一些成功而一些失败,则不会触发回调,这是所有承诺的结果。 IE。它不能给你一个像(伪代码)[success, fail, success, success] 这样的数组——就像人们期望的那样,并且可以在许多 JS 库(ajax、ember 等)中找到。

就像.then 更像是.success,而不是一个在所有承诺都实现后总是运行的函数,无论一些成功还是失败。 为什么没有.when.finally.runThisShizNoMatterWhat??还是我错过了什么(很可能)?

【问题讨论】:

  • 您的问题是有道理的,但寻求帮助并同时攻击设计您使用的语言的人似乎适得其反。
  • 成功正是.then(x) 的意义所在。你在想.then(x,y)。我为 ES6 坚持核心特性而不是走上添加每个人最喜欢的助手的道路而鼓掌。

标签: javascript ecmascript-6 es6-promise


【解决方案1】:

Promise.all 创建一个新的 Promise,它只能作为一个整体来解决或拒绝。也许您可以将其视为具有 every 数组方法语义,当第一个元素与谓词不匹配时返回 false。

then 函数最多接受两个参数,第二个是被拒绝的处理程序。从这个意义上说,它不仅仅是success,它实际上可以处理所有情况。 catch 只是一种方便的方法,是.then(undefined, function(reason) { ... }) 的缩写。

promise API 恐怕没有你需要的东西,你必须自己实现它。

【讨论】:

    【解决方案2】:

    如果其中一个 promise 被拒绝,则 Promise.all 返回的 promise 被拒绝。因此,拒绝处理程序将在其中一个承诺拒绝后立即被调用。如果您只想运行所有 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】:

      这与 Bluebird Promise.all - multiple promises completed aggregating success and rejections 有关,但这是特定于 Bluebird 的。问题的核心是,如果你想检查某件事是成功还是失败,那么你并不是真的在询问每个承诺的直接结果。相反,您需要在使用 Promise.all 之前转换承诺。这个 ES6 标准的 Promise 没有帮助器,但实现起来很简单。在大多数库中,这称为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})
          );
        }));
      }
      

      【讨论】:

        【解决方案4】:

        如果除了价值观之外,你还可以分辨错误,那么做你想做的事就这么简单:

        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));
        &lt;div id="div"&gt;&lt;/div&gt;

        【讨论】:

          【解决方案5】:

          从您的问题看来,您似乎希望解决所有的承诺,promise.all 方法不保证,只有方法 promise.settle 保证解决数组中的每个承诺。

          如果您想要promise.all 的结果,同时还解决每个promise,并通知哪个promise 已解决或拒绝,那么spex.batch 方法正是您所需要的。

          Batch Processing复制的示例:

          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' ]
          

          这只是为了简化对已发生错误列表的快速访问。

          您可以从method's protocol 中看到,您可以传入可选的cb - 回调参数,它将告诉您哪些承诺已解决,哪些承诺被拒绝。

          【讨论】:

            【解决方案6】:

            我同意,如果您使用的是 Bluebird,那么使用 Bluebird.reflect 来实现结算是最好的方法。这是另一种可能很好的解决方案,具体取决于您的用例。它在一个有点有趣的情况下对我有用。这也假设 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
              })
            

            【讨论】:

            • Bluebird.attempt(promise) 没有多大意义,是吗?
            • 在我的情况下确实如此,因为并非数组中的每个函数都返回一个承诺。
            • "不是数组中的每个函数" - 嗯?我以为我们会有一系列的承诺,而不是功能?您的变量名称也表明了这一点。也许你的意思是Promise.resolve
            • 抱歉,变量名称令人困惑。我已经编辑了答案以修复它们。在考虑了您的 cmets 之后,我认为我应该对代码进行更改。函数数组是抛出或返回将成为承诺解决方案的值的函数。所以我认为我应该将这些函数包装在 Promise.method 中,然后将 return Bluebird.attempt(task) 替换为 return task()。我将编辑我的答案以提供更多上下文。
            • 是的,或者那样。 Promise.try (.attempt) 如果你有 taskArray 很好,但 OP 确实有一系列承诺。
            猜你喜欢
            • 2023-04-04
            • 2019-10-08
            • 2017-11-12
            • 1970-01-01
            • 1970-01-01
            • 1970-01-01
            • 1970-01-01
            • 1970-01-01
            • 1970-01-01
            相关资源
            最近更新 更多