Perbedaan pertama - gagal cepat
Saya setuju dengan jawaban @ zzzzBov tetapi keuntungan "gagal cepat" dari Promise.all bukan hanya satu-satunya perbedaan. Beberapa pengguna dalam komentar bertanya mengapa harus menggunakan Promise.all ketika itu hanya lebih cepat dalam skenario negatif (ketika beberapa tugas gagal). Dan saya bertanya mengapa tidak? Jika saya memiliki dua tugas paralel async independen dan yang pertama diselesaikan dalam waktu yang sangat lama tetapi yang kedua ditolak dalam waktu yang sangat singkat mengapa meninggalkan pengguna menunggu pesan kesalahan "waktu sangat lama" dan bukan "waktu yang sangat singkat"? Dalam aplikasi kehidupan nyata kita harus mempertimbangkan skenario negatif. Tapi OK - dalam perbedaan pertama ini Anda dapat memutuskan alternatif mana yang akan digunakan untuk Promise.all vs. beberapa menunggu.
Perbedaan kedua - penanganan kesalahan
Tetapi ketika mempertimbangkan penanganan kesalahan ANDA HARUS menggunakan Promise.all. Tidak mungkin menangani kesalahan tugas paralel async yang dipicu dengan beberapa penantian dengan benar. Dalam skenario negatif Anda akan selalu berakhir dengan UnhandledPromiseRejectionWarning
dan PromiseRejectionHandledWarning
meskipun Anda menggunakan coba / tangkap di mana saja. Karena itulah Promise.all dirancang. Tentu saja seseorang bisa mengatakan bahwa kita dapat menekan kesalahan itu menggunakan process.on('unhandledRejection', err => {})
dan process.on('rejectionHandled', err => {})
tetapi itu bukan praktik yang baik. Saya menemukan banyak contoh di internet yang tidak mempertimbangkan penanganan kesalahan untuk dua atau lebih tugas paralel async independen sama sekali atau menganggapnya tetapi dengan cara yang salah - hanya menggunakan try / catch dan berharap itu akan menangkap kesalahan. Hampir tidak mungkin menemukan latihan yang baik. Itu sebabnya saya menulis jawaban ini.
Ringkasan
Jangan pernah menggunakan beberapa penantian untuk dua atau lebih tugas paralel async independen karena Anda tidak akan dapat menangani kesalahan dengan serius. Selalu gunakan Promise.all () untuk kasus penggunaan ini.
Async / menunggu bukan pengganti untuk Janji. Ini hanya cara bagaimana menggunakan janji ... kode async ditulis dalam gaya sinkronisasi dan kita dapat menghindari banyak then
janji.
Beberapa orang mengatakan bahwa menggunakan Promise.all () kami tidak dapat menangani kesalahan tugas secara terpisah tetapi hanya kesalahan dari janji yang ditolak pertama (ya, beberapa kasus penggunaan mungkin memerlukan penanganan terpisah misalnya untuk penebangan). Itu bukan masalah - lihat "Tambahan" menuju ke bawah.
Contohnya
Pertimbangkan tugas async ini ...
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)
});
};
Saat Anda menjalankan tugas dalam skenario positif, tidak ada perbedaan antara Promise.all dan beberapa menunggu. Kedua contoh berakhir dengan Task 1 succeed! Task 2 succeed!
setelah 5 detik.
// 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!
Ketika tugas pertama mengambil 10 detik dalam skenario positif dan tugas detik membutuhkan 5 detik dalam skenario negatif ada perbedaan dalam kesalahan yang dikeluarkan.
// 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!
Kita seharusnya sudah memperhatikan di sini bahwa kita melakukan sesuatu yang salah ketika menggunakan banyak menunggu secara paralel. Tentu saja untuk menghindari kesalahan, kita harus menanganinya! Mari mencoba...
// 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!
Seperti yang Anda lihat berhasil menangani kesalahan, kita perlu menambahkan hanya satu tangkap ke run
fungsi dan kode dengan tangkap logika dalam panggilan balik ( gaya async ). Kita tidak perlu menangani kesalahan di dalam run
fungsi karena fungsi async berfungsi secara otomatis - janji penolakan task
fungsi menyebabkan penolakan run
fungsi. Untuk menghindari panggilan balik, kita dapat menggunakan gaya sinkronisasi (async / menunggu + coba / tangkap) try { await run(); } catch(err) { }
tetapi dalam contoh ini tidak mungkin karena kita tidak dapat menggunakan await
di utas utama - itu hanya dapat digunakan dalam fungsi async (logis karena tidak ada yang ingin blokir utas). Untuk menguji apakah penanganan berfungsi dalam gaya sinkronisasi, kami dapat meneleponrun
fungsi dari fungsi async lain atau penggunaan Iife (Segera Dipanggil Fungsi Ekspresi): (async function() { try { await run(); } catch(err) { console.log('Caught error', err); }; })();
.
Ini hanya satu cara yang benar bagaimana menjalankan dua atau lebih tugas paralel async dan menangani kesalahan. Anda harus menghindari contoh di bawah ini.
// 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);
};
Kita dapat mencoba menangani kode di atas beberapa cara ...
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
... tidak ada yang tertangkap karena menangani kode sinkronisasi tetapi 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)
... Apa? Kami melihat pertama bahwa kesalahan untuk tugas 2 tidak ditangani dan kemudian yang tertangkap. Menyesatkan dan masih penuh kesalahan di konsol. Tidak bisa digunakan dengan cara ini.
(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)
... sama seperti di atas. Pengguna @Qwerty dalam jawaban yang dihapusnya bertanya tentang perilaku aneh yang tampaknya ditangkap tetapi ada juga kesalahan yang tidak ditangani. Kami menangkap kesalahan karena run () ditolak secara online dengan kata kunci yang menunggu dan dapat ditangkap menggunakan try / catch saat memanggil run (). Kami juga mendapatkan kesalahan tidak tertangani karena kami memanggil fungsi tugas async secara sinkron (tanpa menunggu kata kunci) dan tugas ini berjalan di luar fungsi run () dan juga gagal di luar. Hal ini mirip ketika kita tidak mampu menangani kesalahan dengan mencoba / menangkap saat memanggil beberapa fungsi sync bagian mana dari berjalan kode 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)
... "hanya" dua kesalahan (yang ketiga hilang) tetapi tidak ada yang tertangkap.
Penambahan (menangani kesalahan tugas secara terpisah dan juga kesalahan gagal pertama)
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!
... perhatikan bahwa dalam contoh ini saya menggunakan negativeScenario = true untuk kedua tugas untuk demonstrasi yang lebih baik apa yang terjadi ( throw err
digunakan untuk memecat kesalahan terakhir)