Câu trả lời của Benjamin cung cấp một sự trừu tượng tuyệt vời để giải quyết vấn đề này, nhưng tôi đã hy vọng cho một giải pháp ít trừu tượng hơn. Cách rõ ràng để giải quyết vấn đề này là chỉ cần gọi .catch
các lời hứa nội bộ và trả lại lỗi từ cuộc gọi lại của họ.
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));
Tiến lên một bước nữa, bạn có thể viết một trình xử lý bắt chung chung trông như thế này:
const catchHandler = error => ({ payload: error, resolved: false });
sau đó bạn có thể làm
> 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 } ]
Vấn đề với điều này là các giá trị bị bắt sẽ có giao diện khác với các giá trị không bị bắt, do đó, để làm sạch điều này, bạn có thể làm một cái gì đó như:
const successHandler = result => ({ payload: result, resolved: true });
Vì vậy, bây giờ bạn có thể làm điều này:
> 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!' ]
Sau đó, để giữ cho nó KHÔ, bạn có được câu trả lời của Benjamin:
const reflect = promise => promise
.then(successHandler)
.catch(catchHander)
bây giờ nó trông như thế nào
> 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!' ]
Lợi ích của giải pháp thứ hai là trừu tượng hóa và DRY. Nhược điểm là bạn có nhiều mã hơn, và bạn phải nhớ để phản ánh tất cả các lời hứa của bạn để làm cho mọi thứ nhất quán.
Tôi sẽ mô tả giải pháp của mình là rõ ràng và HÔN, nhưng thực sự kém mạnh mẽ hơn. Giao diện không đảm bảo rằng bạn biết chính xác lời hứa thành công hay thất bại.
Ví dụ bạn có thể có cái này:
const a = Promise.resolve(new Error('Not beaking, just bad'));
const b = Promise.reject(new Error('This actually didnt work'));
Điều này sẽ không bị bắt bởi a.catch
, vì vậy
> Promise.all([a, b].map(promise => promise.catch(e => e))
.then(results => console.log(results))
< [ Error, Error ]
Không có cách nào để biết cái nào gây tử vong và cái nào không. Nếu điều đó quan trọng thì bạn sẽ muốn thực thi và giao diện theo dõi xem nó có thành công hay không (điều reflect
này).
Nếu bạn chỉ muốn xử lý lỗi một cách duyên dáng, thì bạn chỉ có thể coi lỗi là các giá trị không xác định:
> Promise.all([a.catch(() => undefined), b.catch(() => undefined)])
.then((results) => console.log('Known values: ', results.filter(x => typeof x !== 'undefined')))
< [ 'Resolved!' ]
Trong trường hợp của tôi, tôi không cần biết lỗi hoặc lỗi như thế nào - tôi chỉ quan tâm liệu tôi có giá trị hay không. Tôi sẽ để chức năng tạo ra lời hứa lo lắng về việc ghi lại lỗi cụ thể.
const apiMethod = () => fetch()
.catch(error => {
console.log(error.message);
throw error;
});
Bằng cách đó, phần còn lại của ứng dụng có thể bỏ qua lỗi của nó nếu muốn và coi nó là một giá trị không xác định nếu muốn.
Tôi muốn các chức năng cấp cao của mình thất bại một cách an toàn và không lo lắng về các chi tiết về lý do tại sao các phụ thuộc của nó không thành công và tôi cũng thích KISS hơn DRY khi tôi phải thực hiện sự đánh đổi đó - đó là lý do cuối cùng tôi chọn không sử dụng reflect
.