等到所有諾言都完成,即使有些諾言被拒絕

本文翻譯自:Wait until all promises complete even if some rejected

Let's say I have a set of Promise s that are making network requests, of which one will fail: 假設我有一組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   

Let's say I want to wait until all of these have finished, regardless of if one has failed. 假設我想等到所有這些完成爲止,無論是否失敗。 There might be a network error for a resource that I can live without, but which if I can get, I want before I proceed. 我可能無法使用的資源可能會出現網絡錯誤,但是如果可以得到,我會在繼續之前希望這樣做。 I want to handle network failures gracefully. 我想優雅地處理網絡故障。

Since Promises.all doesn't leave any room for this, what is the recommended pattern for handling this, without using a promises library? 由於Promises.all對此沒有任何餘地,因此在不使用promises庫的情況下,推薦的處理方式是什麼?


#1樓

參考:https://stackoom.com/question/27qxl/等到所有諾言都完成-即使有些諾言被拒絕


#2樓

I don't know which promise library you are using, but most have something like allSettled . 我不知道您正在使用哪個Promise庫,但是大多數庫都具有allSettled之類的東西

Edit: Ok since you want to use plain ES6 without external libraries, there is no such method. 編輯:好的,因爲您想使用沒有外部庫的普通ES6,所以沒有這種方法。

In other words: You have to loop over your promises manually and resolve a new combined promise as soon as all promises are settled. 換句話說:您必須手動遍歷您的諾言,並在所有諾言被兌現後立即解決新的合併諾言。


#3樓

Sure, you just need a reflect : 當然,您只需要reflect

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

reflect(promise).then((v => {
    console.log(v.status);
});

Or with ES5: 或使用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);
});

Or in your example: 或在您的示例中:

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

#4樓

I really like Benjamin's answer, and how he basically turns all promises into always-resolving-but-sometimes-with-error-as-a-result ones. 我真的很喜歡本傑明的回答,以及他基本上是如何將所有諾言變成總是能解決但有時會因結果而出錯的。 :) :)
Here's my attempt at your request just in case you were looking for alternatives. 如果您正在尋找替代品,這是我對您的要求的嘗試。 This method simply treats errors as valid results, and is coded similar to Promise.all otherwise: 此方法只是將錯誤視爲有效結果,其編碼類似於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);
  });
}

#5樓

Benjamin's answer offers a great abstraction for solving this issue, but I was hoping for a less abstracted solution. 本傑明的答案爲解決此問題提供了一個很好的抽象,但是我希望有一個不太抽象的解決方案。 The explicit way to to resolve this issue is to simply call .catch on the internal promises, and return the error from their callback. 解決此問題的明確方法是簡單地對內部promise調用.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));

Taking this one step further, you could write a generic catch handler that looks like this: 更進一步,您可以編寫一個通用的catch處理程序,如下所示:

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

then you can do 那你就可以

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

The problem with this is that the caught values will have a different interface than the non-caught values, so to clean this up you might do something like: 這樣做的問題在於,捕獲的值將具有與非捕獲的值不同的接口,因此要清理此問題,您可以執行以下操作:

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

So now you can do this: 現在,您可以執行以下操作:

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

Then to keep it DRY, you get to Benjamin's answer: 然後將其保持乾燥,您會得到本傑明的答案:

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

where it now looks like 現在看起來像什麼

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

The benefits of the second solution are that its abstracted and DRY. 第二個解決方案的好處是抽象和DRY。 The downside is you have more code, and you have to remember to reflect all your promises to make things consistent. 不利的一面是您擁有更多的代碼,並且必須記住要體現所有使事情保持一致的承諾。

I would characterize my solution as explicit and KISS, but indeed less robust. 我將我的解決方案描述爲顯式和KISS,但確實不夠可靠。 The interface doesn't guarantee that you know exactly whether the promise succeeded or failed. 該界面不能保證您確切知道承諾是成功還是失敗。

For example you might have this: 例如,您可能有以下內容:

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

This won't get caught by a.catch , so 這不會被a.catch捕獲,所以

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

There's no way to tell which one was fatal and which was wasn't. 沒有辦法說出哪個是致命的,哪個不是。 If that's important then you're going to want to enforce and interface that tracks whether it was successful or not (which reflect does). 如果這很重要,那麼您將需要實施和接口以跟蹤是否成功(這reflect成功)。

If you just want to handle errors gracefully, then you can just treat errors as undefined values: 如果您只想優雅地處理錯誤,則可以將錯誤視爲未定義的值:

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

In my case, I don't need to know the error or how it failed--I just care whether I have the value or not. 就我而言,我不需要知道錯誤或失敗的方式,我只是在乎我是否有價值。 I'll let the function that generates the promise worry about logging the specific error. 我將讓生成promise的函數擔心記錄特定錯誤。

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

That way, the rest of the application can ignore its error if it wants, and treat it as an undefined value if it wants. 這樣,應用程序的其餘部分可以根據需要忽略其錯誤,並在需要時將其視爲未定義的值。

I want my high level functions to fail safely and not worry about the details on why its dependencies failed, and I also prefer KISS to DRY when I have to make that tradeoff--which is ultimately why I opted to not use reflect . 我希望我的高級函數能夠安全地失敗,並且不必擔心其依賴項失敗的原因的細節,而且在必須進行權衡時,我也更喜歡KISS而不是DRY,這最終就是爲什麼我選擇不使用reflect


#6樓

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

The Promise.all will swallow any rejected promise and store the error in a variable, so it will return when all of the promises have resolved. Promise.all將吞下所有被拒絕的promise,並將錯誤存儲在變量中,因此當所有promise被解決後,它將返回。 Then you can re-throw the error out, or do whatever. 然後,您可以將錯誤重新拋出,或者執行任何操作。 In this way, I guess you would get out the last rejection instead of the first one. 這樣,我想您將獲得最後一個拒絕而不是第一個拒絕。

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章