ความแตกต่างแรก - ล้มเหลวอย่างรวดเร็ว
ฉันเห็นด้วยกับคำตอบของ @ zzzzBov แต่ข้อดี "ไม่เร็ว" ของ Promise.all ไม่ได้เป็นเพียงข้อแตกต่าง ผู้ใช้บางคนในความคิดเห็นถามว่าเหตุใดจึงต้องใช้ Promise.all เมื่อมันเร็วขึ้นในสถานการณ์เชิงลบเท่านั้น (เมื่องานบางงานล้มเหลว) และฉันถามว่าทำไมไม่ หากฉันมีสองงานแบบอะซิงโครนัสแบบอิสระแยกกันและงานชิ้นแรกได้รับการแก้ไขในเวลานานมาก แต่งานที่สองถูกปฏิเสธในเวลาสั้น ๆ เหตุใดผู้ใช้จึงต้องรอให้เกิดข้อผิดพลาด "นานมาก" แทนที่จะเป็น ในการใช้งานจริงเราต้องพิจารณาสถานการณ์เชิงลบ แต่ตกลง - ในความแตกต่างแรกนี้คุณสามารถเลือกได้ว่าจะใช้ทางเลือกใดกับ Promise.all
ข้อแตกต่างที่สอง - การจัดการข้อผิดพลาด
แต่เมื่อพิจารณาข้อผิดพลาดในการจัดการคุณต้องใช้ Promise.all เป็นไปไม่ได้ที่จะจัดการข้อผิดพลาดของงานแบบขนาน async ที่ถูกกระตุ้นด้วยการรอหลายครั้งอย่างถูกต้อง ในสถานการณ์เชิงลบคุณจะจบลงด้วยUnhandledPromiseRejectionWarning
และPromiseRejectionHandledWarning
แม้ว่าคุณจะใช้ลอง / จับได้ทุกที่ นั่นคือเหตุผลที่ Promise.all ได้รับการออกแบบ แน่นอนว่าบางคนสามารถพูดได้ว่าเราสามารถระงับข้อผิดพลาดที่ใช้process.on('unhandledRejection', err => {})
และprocess.on('rejectionHandled', err => {})
มันไม่ใช่วิธีปฏิบัติที่ดี ฉันพบตัวอย่างมากมายบนอินเทอร์เน็ตที่ไม่พิจารณาข้อผิดพลาดในการจัดการ async แบบขนานอย่างอิสระสองงานหรือมากกว่าหรือพิจารณา แต่ในทางที่ผิด - เพียงแค่ใช้ลอง / จับและหวังว่ามันจะจับข้อผิดพลาด แทบเป็นไปไม่ได้เลยที่จะหาวิธีปฏิบัติที่ดี นั่นคือเหตุผลที่ฉันเขียนคำตอบนี้
สรุป
อย่าใช้งานหลาย ๆ อย่างรอคอยสำหรับงาน async แบบขนานอย่างน้อยสองงานเนื่องจากคุณจะไม่สามารถจัดการข้อผิดพลาดได้อย่างจริงจัง ใช้ Promise.all () เสมอสำหรับกรณีการใช้งานนี้
Async / await ไม่ได้มาแทนที่ Promises มันเป็นวิธีการใช้สัญญาสวย ๆ ... รหัส async ถูกเขียนในรูปแบบการซิงค์และเราสามารถหลีกเลี่ยงได้หลายอย่างthen
ในสัญญา
บางคนบอกว่าการใช้ Promise.all () เราไม่สามารถจัดการข้อผิดพลาดของงานแยกต่างหาก แต่เกิดจากข้อผิดพลาดจากสัญญาที่ถูกปฏิเสธครั้งแรก (ใช่บางกรณีการใช้งานอาจต้องการการจัดการแยกต่างหากเช่นสำหรับการบันทึก) ไม่มีปัญหา - ดูหัวข้อ "การเพิ่ม" ด้านล่าง
ตัวอย่าง
พิจารณาภารกิจ async นี้ ...
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)
});
};
เมื่อคุณเรียกใช้งานในสถานการณ์เชิงบวกไม่มีความแตกต่างระหว่าง Promise.all และหลายรอ ตัวอย่างทั้งสองลงท้ายด้วยTask 1 succeed! Task 2 succeed!
หลังจาก 5 วินาที
// 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!
เมื่องานแรกใช้เวลา 10 วินาทีในสถานการณ์เชิงบวกและงานวินาทีใช้เวลา 5 วินาทีในสถานการณ์เชิงลบมีความแตกต่างในข้อผิดพลาดออก
// 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!
เราควรสังเกตุที่นี่แล้วว่าเรากำลังทำอะไรผิดพลาดเมื่อใช้งานหลายรายการพร้อมกัน แน่นอนเพื่อหลีกเลี่ยงข้อผิดพลาดเราควรจัดการกับมัน! มาลองกัน...
// 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!
ในขณะที่คุณสามารถเห็นข้อผิดพลาดในการจัดการที่ประสบความสำเร็จเราจำเป็นต้องเพิ่มหนึ่ง catch to run
function และโค้ดที่มีลอจิก catch อยู่ใน callback ( async style ) เราไม่จำเป็นต้องจัดการกับข้อผิดพลาดภายในrun
ฟังก์ชั่นเพราะฟังก์ชั่น async มันทำโดยอัตโนมัติ - การปฏิเสธสัญญาของtask
ฟังก์ชั่นทำให้เกิดการปฏิเสธrun
ฟังก์ชั่น เพื่อหลีกเลี่ยงการโทรกลับเราสามารถใช้รูปแบบการซิงค์ (async / await + try / catch) try { await run(); } catch(err) { }
แต่ในตัวอย่างนี้มันเป็นไปไม่ได้เพราะเราไม่สามารถใช้await
ในเธรดหลักได้ - มันสามารถใช้ได้เฉพาะในฟังก์ชั่น async (เป็นตรรกะเพราะไม่มีใครต้องการ บล็อกเธรดหลัก) เพื่อทดสอบว่าการจัดการทำงานในลักษณะซิงค์เราสามารถโทรrun
ฟังก์ชั่นจากฟังก์ชั่น async อื่นหรือใช้ IIFE (รื้อฟื้นทันที Expression (async function() { try { await run(); } catch(err) { console.log('Caught error', err); }; })();
ฟังก์ชั่น):
นี่เป็นวิธีที่ถูกต้องเพียงวิธีเดียวในการเรียกใช้งาน async แบบขนานสองงานขึ้นไปและจัดการข้อผิดพลาด คุณควรหลีกเลี่ยงตัวอย่างด้านล่าง
// 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);
};
เราสามารถลองจัดการโค้ดด้านบนได้หลายวิธี ...
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
... ไม่มีสิ่งใดถูกจับได้เพราะมันจัดการกับรหัสซิงค์ แต่run
เป็น 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)
... เหรอ? เราเห็นก่อนว่าข้อผิดพลาดสำหรับงาน 2 นั้นไม่ได้รับการจัดการและต่อมาที่ถูกตรวจจับ ทำให้เข้าใจผิดและยังเต็มไปด้วยข้อผิดพลาดในคอนโซล ใช้วิธีนี้ไม่ได้
(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)
... เหมือนข้างบน ผู้ใช้ @Qwerty ในคำตอบที่ถูกลบของเขาถามเกี่ยวกับพฤติกรรมแปลก ๆ ที่ดูเหมือนจะถูกจับ แต่ก็มีข้อผิดพลาดที่ไม่สามารถจัดการได้ เราตรวจจับข้อผิดพลาดเนื่องจาก run () ถูกปฏิเสธตามคำหลักที่รอและสามารถจับได้โดยใช้ try / catch เมื่อเรียกใช้ run () นอกจากนี้เรายังได้รับข้อผิดพลาดที่ไม่สามารถจัดการได้เพราะเรากำลังเรียกฟังก์ชั่นงาน async แบบซิงโครนัส มันก็คล้าย ๆ เมื่อเราไม่สามารถที่จะจัดการกับข้อผิดพลาดโดยลอง / จับเมื่อเรียกฟังก์ชั่นการซิงค์บางส่วนซึ่งเป็นส่วนหนึ่งของรหัสที่วิ่งใน 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)
... ข้อผิดพลาดสองข้อ "เท่านั้น" (อันที่สามหายไป) แต่ไม่พบสิ่งใด
เพิ่มเติม (จัดการข้อผิดพลาดในงานแยกต่างหากและข้อผิดพลาดที่เกิดจากความล้มเหลวก่อน)
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!
... โปรดทราบว่าในตัวอย่างนี้ฉันใช้ negativeScenario = true สำหรับทั้งสองงานเพื่อการสาธิตที่ดียิ่งขึ้นว่าเกิดอะไรขึ้น ( throw err
ใช้เพื่อยิงข้อผิดพลาดสุดท้าย)