JavaScript Promise.all 是不是有在成功和失败时触发的回调 [重复]

Posted

技术标签:

【中文标题】JavaScript Promise.all 是不是有在成功和失败时触发的回调 [重复]【英文标题】: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 坚持核心特性而不是走上添加每个人最喜欢的助手的道路而鼓掌。 【参考方案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 确实有一系列承诺。

以上是关于JavaScript Promise.all 是不是有在成功和失败时触发的回调 [重复]的主要内容,如果未能解决你的问题,请参考以下文章

如何判断我的 promise.all 是不是并行运行?

JavaScript的Promise.all和Promise.race

javascript 迭代数组,知道什么时候完成promise所有promise.all

Javascript 像 Java 8 上的“Promise.all”(可能带有 lambdas)

JavaScript Promise.all - 如何检查解析状态?

javascript Promise.all示例