小编典典

等到所有承诺完成,即使有些被拒绝

all

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

// 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

假设我想等到所有这些都完成,无论是否失败。对于我可以没有的资源,可能存在网络错误,但如果我能得到,我会在继续之前想要。我想优雅地处理网络故障。

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


阅读 143

收藏
2022-03-11

共1个答案

小编典典

本杰明的回答为解决这个问题提供了一个很好的抽象,但我希望有一个不那么抽象的解决方案。解决此问题的显式方法是简单地调用.catch内部承诺,并从其回调中返回错误。

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

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

const catchHandler = error => ({ payload: error, resolved: false });

那么你可以做

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

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

const successHandler = result => ({ payload: result, resolved: true });

所以现在你可以这样做:

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

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

const reflect = promise => promise
  .then(successHandler)
  .catch(catchHander)

现在的样子

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

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

我会将我的解决方案描述为显式和 KISS,但确实不太健壮。该接口不能保证您确切知道承诺是成功还是失败。

例如你可能有这个:

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

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

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

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

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

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

就我而言,我不需要知道错误或它是如何失败的——我只关心我是否有价值。我会让生成承诺的函数担心记录特定的错误。

const apiMethod = () => fetch()
  .catch(error => {
    console.log(error.message);
    throw error;
  });

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

我希望我的高级函数能够安全地失败,而不是担心它的依赖项失败的细节,而且当我必须做出这种权衡时,我也更喜欢 KISS 而不是
DRY——这最终是我选择不使用reflect.

2022-03-11