等到所有ES6承诺完成,甚至拒绝承诺

Nat*_*gen 351 javascript promise es6-promise

假设我有一组承诺正在发出网络请求,其中一个会失败:

// http://does-not-exist will throw a TypeError
var arr = [ fetch('index.html'), fetch('http://does-not-exist') ]

Promise.all(arr)
  .then(res => console.log('success', res))
  .catch(err => console.log('error', err)) // This is executed   
Run Code Online (Sandbox Code Playgroud)

让我们说我想等到所有这些都结束了,不管一个人是否失败了.对于我可以没有的资源,可能存在网络错误,但如果我可以获得,我希望在继续之前.我想优雅地处理网络故障.

既然Promise没有留下任何空间,那么在不使用promises库的情况下,处理此问题的推荐模式是什么?

Ben*_*aum 278

当然,你只需要一个reflect:

const reflect = p => p.then(v => ({v, status: "fulfilled" }),
                            e => ({e, status: "rejected" }));

reflect(promise).then((v => {
    console.log(v.status);
});
Run Code Online (Sandbox Code Playgroud)

或者使用ES5:

function reflect(promise){
    return promise.then(function(v){ return {v:v, status: "fulfilled" }},
                        function(e){ return {e:e, status: "rejected" }});
}


reflect(promise).then(function(v){
    console.log(v.status);
});
Run Code Online (Sandbox Code Playgroud)

或者在你的例子中:

var arr = [ fetch('index.html'), fetch('http://does-not-exist') ]

Promise.all(arr.map(reflect)).then(function(results){
    var success = results.filter(x => x.status === "fulfilled");
});
Run Code Online (Sandbox Code Playgroud)

  • 为了回应我自己的问题,我创建了以下npm包:https://github.com/Bucabug/promise-reflect https://www.npmjs.com/package/promise-reflect (4认同)
  • 我认为这是一个很好的解决方案.你可以修改它以包含更简单的语法吗?问题的关键在于,如果你想处理子承诺中的错误,你应该捕获它们并返回错误.例如:https://gist.github.com/nhagen/a1d36b39977822c224b8 (3认同)
  • @NathanHagen它可以让你找出被拒绝的内容和实现的内容,并将问题提取给可重用的运算符. (3认同)
  • “ reflect”一词在计算机科学中是一个常见的词吗?您能否链接到维基百科之类的解释内容。我一直在努力寻找Promise。所有人甚至都没有首先拒绝过,但不知道要搜索“ Reflect”。ES6是否应该有一个类似于“ Promise.all但实际上全部”的Promise.reflect? (3认同)
  • 我刚刚遇到这个问题,我为它创建了这个npm包:https://www.npmjs.com/package/promise-all-soft-fail (2认同)

jib*_*jib 233

类似的答案,但更可能是ES6的惯用语:

const a = Promise.resolve(1);
const b = Promise.reject(new Error(2));
const c = Promise.resolve(3);

Promise.all([a, b, c].map(p => p.catch(e => e)))
  .then(results => console.log(results)) // 1,Error: 2,3
  .catch(e => console.log(e));


const console = { log: msg => div.innerHTML += msg + "<br>"};
Run Code Online (Sandbox Code Playgroud)
<div id="div"></div>
Run Code Online (Sandbox Code Playgroud)

根据返回的值的类型,通常可以很容易地区分错误(例如,undefined用于"不关心",typeof用于普通的非对象值result.message,result.toString().startsWith("Error:")等等)

  • `.catch(e => console.log(e));`永远不会被调用,因为它永远不会失败 (34认同)
  • @ bfred.it那是对的.虽然终止使用`catch`的承诺链通常是好习惯[恕我直言](http://stackoverflow.com/questions/33376308/es6-promises-swallow-type-errors/33382482#33382482). (3认同)
  • @KarlBateman 我认为你很困惑。顺序函数resolve或reject在这里并不重要,因为`.map(p =&gt; p.catch(e =&gt; e))`部分将所有拒绝转换为解析值,所以`Promise.all`仍然等待一切完成各个函数是否解决或拒绝,无论它们需要多长时间。尝试一下。 (2认同)
  • @SuhailGupta它捕获错误`e`并将其作为常规(成功)值返回.与`p.catch(function(e){return e;})相同``仅更短.`return`是隐含的. (2认同)

Nat*_*gen 65

Benjamin的回答为解决这个问题提供了很好的抽象,但我希望得到一个不那么抽象的解决方案.解决此问题的明确方法是简单地调用.catch内部promise,并从其回调中返回错误.

let a = new Promise((res, rej) => res('Resolved!')),
    b = new Promise((res, rej) => rej('Rejected!')),
    c = a.catch(e => { console.log('"a" failed.'); return e; }),
    d = b.catch(e => { console.log('"b" failed.'); return e; });

Promise.all([c, d])
  .then(result => console.log('Then', result)) // Then ["Resolved!", "Rejected!"]
  .catch(err => console.log('Catch', err));

Promise.all([a.catch(e => e), b.catch(e => e)])
  .then(result => console.log('Then', result)) // Then ["Resolved!", "Rejected!"]
  .catch(err => console.log('Catch', err));
Run Code Online (Sandbox Code Playgroud)

更进一步,您可以编写一个通用的catch处理程序,如下所示:

const catchHandler = error => ({ payload: error, resolved: false });
Run Code Online (Sandbox Code Playgroud)

那么你可以做到

> Promise.all([a, b].map(promise => promise.catch(catchHandler))
    .then(results => console.log(results))
    .catch(() => console.log('Promise.all failed'))
< [ 'Resolved!',  { payload: Promise, resolved: false } ]
Run Code Online (Sandbox Code Playgroud)

这个问题是捕获的值将具有与未捕获的值不同的接口,因此要清除它,您可能会执行以下操作:

const successHandler = result => ({ payload: result, resolved: true });
Run Code Online (Sandbox Code Playgroud)

所以现在你可以这样做:

> Promise.all([a, b].map(result => result.then(successHandler).catch(catchHandler))
    .then(results => console.log(results.filter(result => result.resolved))
    .catch(() => console.log('Promise.all failed'))
< [ 'Resolved!' ]
Run Code Online (Sandbox Code Playgroud)

然后为了保持干燥,你得到本杰明的回答:

const reflect = promise => promise
  .then(successHandler)
  .catch(catchHander)
Run Code Online (Sandbox Code Playgroud)

它现在看起来像

> Promise.all([a, b].map(result => result.then(successHandler).catch(catchHandler))
    .then(results => console.log(results.filter(result => result.resolved))
    .catch(() => console.log('Promise.all failed'))
< [ 'Resolved!' ]
Run Code Online (Sandbox Code Playgroud)

第二种解决方案的好处是它的抽象和干燥.缺点是你有更多的代码,你必须记住反映你所有的承诺,使事情保持一致.

我将我的解决方案描述为明确的和KISS,但实际上不那么健壮.界面不保证您确切知道承诺是成功还是失败.

例如,你可能有这个:

const a = Promise.resolve(new Error('Not beaking, just bad'));
const b = Promise.reject(new Error('This actually didnt work'));
Run Code Online (Sandbox Code Playgroud)

这不会被抓住a.catch,所以

> Promise.all([a, b].map(promise => promise.catch(e => e))
    .then(results => console.log(results))
< [ Error, Error ]
Run Code Online (Sandbox Code Playgroud)

没有办法分辨哪一个是致命的,哪个不是.如果这很重要,那么你将需要强制执行和界面来跟踪它是否成功(reflect确实如此).

如果您只是想要优雅地处理错误,那么您可以将错误视为未定义的值:

> Promise.all([a.catch(() => undefined), b.catch(() => undefined)])
    .then((results) => console.log('Known values: ', results.filter(x => typeof x !== 'undefined')))
< [ 'Resolved!' ]
Run Code Online (Sandbox Code Playgroud)

在我的情况下,我不需要知道错误或失败 - 我只关心我是否有价值.我会让生成promise的函数担心记录特定的错误.

const apiMethod = () => fetch()
  .catch(error => {
    console.log(error.message);
    throw error;
  });
Run Code Online (Sandbox Code Playgroud)

这样,应用程序的其余部分可以在需要时忽略其错误,并在需要时将其视为未定义的值.

我希望我的高级功能安全地失败,不要担心它的依赖性失败的细节,而且当我必须做出权衡时我也更喜欢KISS干 - 这最终是我选择不使用的原因reflect.

  • 这是一个糟糕的想法的原因是因为它将Promise实现与仅在特定的```Promise.all()```变体中使用的特定用例相关联,然后它也成为Promise消费者的责任者知道具体的承诺不会拒绝,但会吞下它的错误.事实上,```reflect()```方法可以通过调用它来减少'抽象'和更明确的方式```PromiseEvery(promises).then(...)```.上面答案的复杂性与本杰明相比,应该多说一下这个解决方案. (3认同)
  • @ LUH3417这个解决方案在概念上不太合理,因为它将错误视为值并且不会将错误与非错误分开.例如,如果其中一个promise合法地解析为可以抛出的值(这完全可能),那么这会非常糟糕. (2认同)
  • @BenjaminGruenbaum所以例如,`new Promise((res,rej)=> res(new Error('Legitimate error'))`与`new Promise((res,rej)=> rej(new error)无法区分('非法错误'))`或者更进一步,您将无法通过`x.status`过滤?我会将这一点添加到我的答案中,因此区别更明显 (2认同)

Cer*_*nce 17

在香草Javascript中,提出了一个可以通过本机完成此功能的建议Promise.allSettled。它目前处于第4阶段,很有可能将其纳入正式规格。它与其他答案中reflect功能非常相似。这是来自提案页面的示例。在此之前,您必须做以下事情:

function reflect(promise) {
  return promise.then(
    (v) => {
      return { status: 'fulfilled', value: v };
    },
    (error) => {
      return { status: 'rejected', reason: error };
    }
  );
}

const promises = [ fetch('index.html'), fetch('https://does-not-exist/') ];
const results = await Promise.all(promises.map(reflect));
const successfulPromises = results.filter(p => p.status === 'fulfilled');
Run Code Online (Sandbox Code Playgroud)

使用Promise.allSettled代替,上面将等同于:

const promises = [ fetch('index.html'), fetch('https://does-not-exist/') ];
const results = await Promise.allSettled(promises);
const successfulPromises = results.filter(p => p.status === 'fulfilled');
Run Code Online (Sandbox Code Playgroud)

一旦成为规范的一部分并且浏览器实现了它,您就可以在没有任何库的现代浏览器中使用它。

Chrome 76附带了该代码,因此在其中运行的以下代码段应该没有问题:

function reflect(promise) {
  return promise.then(
    (v) => {
      return { status: 'fulfilled', value: v };
    },
    (error) => {
      return { status: 'rejected', reason: error };
    }
  );
}

const promises = [ fetch('index.html'), fetch('https://does-not-exist/') ];
const results = await Promise.all(promises.map(reflect));
const successfulPromises = results.filter(p => p.status === 'fulfilled');
Run Code Online (Sandbox Code Playgroud)

输出:

const promises = [ fetch('index.html'), fetch('https://does-not-exist/') ];
const results = await Promise.allSettled(promises);
const successfulPromises = results.filter(p => p.status === 'fulfilled');
Run Code Online (Sandbox Code Playgroud)

在此期间,有一个符合规范的填充工具在这里

  • 即使其他答案仍然有效,这个答案应该得到更多的支持,因为它是解决这个问题的最新方法。 (2认同)

Kub*_*tek 9

我真的很喜欢本杰明的答案,以及他如何将所有的承诺变成永远解决但有时与错误相关的结果.:)
这是我尝试你的请求,以防万一你正在寻找替代品.此方法只是将错误视为有效结果,并且编码类似于Promise.all其他方式:

Promise.settle = function(promises) {
  var results = [];
  var done = promises.length;

  return new Promise(function(resolve) {
    function tryResolve(i, v) {
      results[i] = v;
      done = done - 1;
      if (done == 0)
        resolve(results);
    }

    for (var i=0; i<promises.length; i++)
      promises[i].then(tryResolve.bind(null, i), tryResolve.bind(null, i));
    if (done == 0)
      resolve(results);
  });
}
Run Code Online (Sandbox Code Playgroud)

  • 好的,*定居*确实是一个更好的名字.:) (2认同)

mar*_*770 5

var err;
Promise.all([
    promiseOne().catch(function(error) { err = error;}),
    promiseTwo().catch(function(error) { err = error;})
]).then(function() {
    if (err) {
        throw err;
    }
});
Run Code Online (Sandbox Code Playgroud)

Promise.all会吞噬任何拒绝承诺并存储错误的变量,所以当所有的承诺都得到解决,它将返回。然后你可以重新抛出错误,或者做任何事情。这样,我猜你会得到最后一个拒绝而不是第一个。