Khác biệt đầu tiên - thất bại nhanh
Tôi đồng ý với câu trả lời của @ zzzzBov nhưng lợi thế "không nhanh" của Promise.all không chỉ là một điểm khác biệt. Một số người dùng trong các bình luận hỏi tại sao nên sử dụng Promise.all khi nó chỉ nhanh hơn trong kịch bản tiêu cực (khi một số tác vụ thất bại). Và tôi hỏi tại sao không? Nếu tôi có hai tác vụ song song không đồng bộ độc lập và lần đầu tiên được giải quyết trong thời gian rất dài nhưng lần thứ hai bị từ chối trong thời gian rất ngắn, tại sao lại để người dùng chờ đợi thông báo lỗi "thời gian rất dài" thay vì "thời gian rất ngắn"? Trong các ứng dụng thực tế, chúng ta phải xem xét kịch bản tiêu cực. Nhưng OK - trong sự khác biệt đầu tiên này, bạn có thể quyết định lựa chọn nào để sử dụng Promise.all so với nhiều chờ đợi.
Khác biệt thứ hai - xử lý lỗi
Nhưng khi xem xét xử lý lỗi BẠN PHẢI sử dụng Promise.all. Không thể xử lý chính xác các lỗi của các tác vụ song song không đồng bộ được kích hoạt với nhiều chờ đợi. Trong trường hợp tiêu cực, bạn sẽ luôn kết thúc UnhandledPromiseRejectionWarning
và PromiseRejectionHandledWarning
mặc dù bạn sử dụng thử / bắt ở bất cứ đâu. Đó là lý do tại sao Promise.all được thiết kế. Tất nhiên ai đó có thể nói rằng chúng ta có thể ngăn chặn lỗi đó bằng cách sử dụng process.on('unhandledRejection', err => {})
vàprocess.on('rejectionHandled', err => {})
đó không phải là thực tiễn tốt. Tôi đã tìm thấy nhiều ví dụ trên internet không xem xét xử lý lỗi đối với hai hoặc nhiều tác vụ song song không đồng bộ độc lập hoặc xem xét sai nhưng chỉ sử dụng thử / bắt và hy vọng nó sẽ bắt lỗi. Hầu như không thể tìm thấy thực hành tốt. Đó là lý do tại sao tôi viết câu trả lời này.
Tóm lược
Không bao giờ sử dụng nhiều chờ đợi cho hai hoặc nhiều tác vụ song song không đồng bộ độc lập vì bạn sẽ không thể xử lý lỗi nghiêm trọng. Luôn sử dụng Promise.all () cho trường hợp sử dụng này.
Async / await không thay thế cho Promise. Đây chỉ là một cách khá hay để sử dụng lời hứa ... mã async được viết theo kiểu đồng bộ hóa và chúng ta có thể tránh được nhiềuthen
lời hứa.
Một số người nói rằng sử dụng Promise.all () chúng tôi không thể xử lý các lỗi nhiệm vụ một cách riêng biệt mà chỉ có lỗi từ lời hứa bị từ chối đầu tiên (vâng, một số trường hợp sử dụng có thể yêu cầu xử lý riêng, ví dụ như để đăng nhập). Đây không phải là vấn đề - xem tiêu đề "Bổ sung" bên dưới.
Ví dụ
Hãy xem xét nhiệm vụ không đồng bộ này ...
const task = function(taskNum, seconds, negativeScenario) {
return new Promise((resolve, reject) => {
setTimeout(_ => {
if (negativeScenario)
reject(new Error('Task ' + taskNum + ' failed!'));
else
resolve('Task ' + taskNum + ' succeed!');
}, seconds * 1000)
});
};
Khi bạn chạy các tác vụ trong kịch bản tích cực, không có sự khác biệt giữa Promise.all và nhiều sự chờ đợi. Cả hai ví dụ kết thúc Task 1 succeed! Task 2 succeed!
sau 5 giây.
// Promise.all alternative
const run = async function() {
// tasks run immediate in parallel and wait for both results
let [r1, r2] = await Promise.all([
task(1, 5, false),
task(2, 5, false)
]);
console.log(r1 + ' ' + r2);
};
run();
// at 5th sec: Task 1 succeed! Task 2 succeed!
// multiple await alternative
const run = async function() {
// tasks run immediate in parallel
let t1 = task(1, 5, false);
let t2 = task(2, 5, false);
// wait for both results
let r1 = await t1;
let r2 = await t2;
console.log(r1 + ' ' + r2);
};
run();
// at 5th sec: Task 1 succeed! Task 2 succeed!
Khi tác vụ đầu tiên mất 10 giây trong kịch bản tích cực và nhiệm vụ giây mất 5 giây trong kịch bản tiêu cực, có sự khác biệt về lỗi được đưa ra.
// Promise.all alternative
const run = async function() {
let [r1, r2] = await Promise.all([
task(1, 10, false),
task(2, 5, true)
]);
console.log(r1 + ' ' + r2);
};
run();
// at 5th sec: UnhandledPromiseRejectionWarning: Error: Task 2 failed!
// multiple await alternative
const run = async function() {
let t1 = task(1, 10, false);
let t2 = task(2, 5, true);
let r1 = await t1;
let r2 = await t2;
console.log(r1 + ' ' + r2);
};
run();
// at 5th sec: UnhandledPromiseRejectionWarning: Error: Task 2 failed!
// at 10th sec: PromiseRejectionHandledWarning: Promise rejection was handled asynchronously (rejection id: 1)
// at 10th sec: UnhandledPromiseRejectionWarning: Error: Task 2 failed!
Chúng ta nên lưu ý ở đây rằng chúng ta đang làm gì đó sai khi sử dụng song song nhiều chờ đợi. Tất nhiên để tránh lỗi chúng ta nên xử lý nó! Hãy thử ...
// Promise.all alternative
const run = async function() {
let [r1, r2] = await Promise.all([
task(1, 10, false),
task(2, 5, true)
]);
console.log(r1 + ' ' + r2);
};
run().catch(err => { console.log('Caught error', err); });
// at 5th sec: Caught error Error: Task 2 failed!
Như bạn có thể thấy để xử lý thành công lỗi, chúng ta cần thêm chỉ một run
hàm vào hàm và mã với logic bắt là trong hàm gọi lại ( kiểu async ). Chúng tôi không cần xử lý các lỗi bên trong run
chức năng vì chức năng async tự động thực hiện - việc từ chối task
chức năng gây ra từ chối run
chức năng. Để tránh gọi lại, chúng tôi có thể sử dụng kiểu đồng bộ hóa (async / await + try / Catch) try { await run(); } catch(err) { }
nhưng trong ví dụ này không thể vì chúng tôi không thể sử dụng await
trong luồng chính - nó chỉ có thể được sử dụng trong chức năng async (nó hợp lý vì không ai muốn khối chủ đề chính). Để kiểm tra xem việc xử lý có hoạt động trong chức năng kiểu đồng bộ hóa chúng ta có thể gọirun
từ một chức năng không đồng bộ khác hay sử dụng IIFE (Biểu thức chức năng được gọi ngay lập tức) : (async function() { try { await run(); } catch(err) { console.log('Caught error', err); }; })();
.
Đây chỉ là một cách chính xác để chạy hai hoặc nhiều tác vụ song song không đồng bộ và xử lý lỗi. Bạn nên tránh các ví dụ dưới đây.
// multiple await alternative
const run = async function() {
let t1 = task(1, 10, false);
let t2 = task(2, 5, true);
let r1 = await t1;
let r2 = await t2;
console.log(r1 + ' ' + r2);
};
Chúng tôi có thể cố gắng xử lý mã ở trên một số cách ...
try { run(); } catch(err) { console.log('Caught error', err); };
// at 5th sec: UnhandledPromiseRejectionWarning: Error: Task 2 failed!
// at 10th sec: UnhandledPromiseRejectionWarning: Error: Task 2 failed!
// at 10th sec: PromiseRejectionHandledWarning: Promise rejection was handled
... không có gì bị bắt vì nó xử lý đang sync nhưng run
là async
run().catch(err => { console.log('Caught error', err); });
// at 5th sec: UnhandledPromiseRejectionWarning: Error: Task 2 failed!
// at 10th sec: Caught error Error: Task 2 failed!
// at 10th sec: PromiseRejectionHandledWarning: Promise rejection was handled asynchronously (rejection id: 1)
... Wtf? Trước tiên, chúng tôi thấy rằng lỗi cho nhiệm vụ 2 đã không được xử lý và sau đó đã bị bắt. Gây hiểu lầm và vẫn đầy lỗi trong giao diện điều khiển. Không thể sử dụng theo cách này.
(async function() { try { await run(); } catch(err) { console.log('Caught error', err); }; })();
// at 5th sec: UnhandledPromiseRejectionWarning: Error: Task 2 failed!
// at 10th sec: Caught error Error: Task 2 failed!
// at 10th sec: PromiseRejectionHandledWarning: Promise rejection was handled asynchronously (rejection id: 1)
... như trên. Người dùng @Qwerty trong câu trả lời đã xóa của mình đã hỏi về hành vi kỳ lạ này dường như bị bắt nhưng cũng có những lỗi chưa được xử lý. Chúng tôi bắt lỗi vì run () bị từ chối trên dòng với từ khóa đang chờ và có thể bị bắt bằng cách sử dụng try / Catch khi gọi run (). Chúng tôi cũng nhận được lỗi chưa được xử lý vì chúng tôi đang gọi hàm tác vụ async một cách đồng bộ (không có từ khóa chờ) và tác vụ này chạy bên ngoài hàm run () và cũng bị lỗi bên ngoài. Nó tương tự như khi chúng tôi không thể xử lý lỗi bằng try / catch khi gọi một số chức năng đồng bộ hóa mà một phần của mã chạy trong setTimeout ... function test() { setTimeout(function() { console.log(causesError); }, 0); }; try { test(); } catch(e) { /* this will never catch error */ }
.
const run = async function() {
try {
let t1 = task(1, 10, false);
let t2 = task(2, 5, true);
let r1 = await t1;
let r2 = await t2;
}
catch (err) {
return new Error(err);
}
console.log(r1 + ' ' + r2);
};
run().catch(err => { console.log('Caught error', err); });
// at 5th sec: UnhandledPromiseRejectionWarning: Error: Task 2 failed!
// at 10th sec: PromiseRejectionHandledWarning: Promise rejection was handled asynchronously (rejection id: 1)
... "Chỉ" hai lỗi (thiếu thứ 3) nhưng không có gì bị bắt.
Ngoài ra (xử lý các lỗi nhiệm vụ riêng biệt và cả lỗi đầu tiên)
const run = async function() {
let [r1, r2] = await Promise.all([
task(1, 10, true).catch(err => { console.log('Task 1 failed!'); throw err; }),
task(2, 5, true).catch(err => { console.log('Task 2 failed!'); throw err; })
]);
console.log(r1 + ' ' + r2);
};
run().catch(err => { console.log('Run failed (does not matter which task)!'); });
// at 5th sec: Task 2 failed!
// at 5th sec: Run failed (does not matter which task)!
// at 10th sec: Task 1 failed!
... lưu ý rằng trong ví dụ này tôi đã sử dụng negScenario = true cho cả hai tác vụ để thể hiện tốt hơn những gì xảy ra ( throw err
được sử dụng để bắn lỗi cuối cùng)