Adakah perbedaan antara menunggu Promise.all () dan beberapa menunggu?

181

Apakah ada perbedaan antara:

const [result1, result2] = await Promise.all([task1(), task2()]);

dan

const t1 = task1();
const t2 = task2();

const result1 = await t1;
const result2 = await t2;

dan

const [t1, t2] = [task1(), task2()];
const [result1, result2] = [await t1, await t2];
Tersembunyi
sumber

Jawaban:

209

Catatan :

Jawaban ini hanya mencakup perbedaan waktu antara awaitseri dan Promise.all. Pastikan untuk membaca jawaban komprehensif @ mikep yang juga mencakup perbedaan yang lebih penting dalam penanganan kesalahan .


Untuk keperluan jawaban ini saya akan menggunakan beberapa metode contoh:

  • res(ms) adalah fungsi yang mengambil bilangan bulat milidetik dan mengembalikan janji yang diselesaikan setelah banyak milidetik.
  • rej(ms) adalah fungsi yang mengambil bilangan bulat milidetik dan mengembalikan janji yang menolak setelah itu banyak milidetik.

Panggilan resmemulai penghitung waktu. Menggunakan Promise.alluntuk menunggu beberapa penundaan akan menyelesaikan setelah semua penundaan selesai, tetapi ingat mereka mengeksekusi pada saat yang sama:

Contoh 1
const data = await Promise.all([res(3000), res(2000), res(1000)])
//                              ^^^^^^^^^  ^^^^^^^^^  ^^^^^^^^^
//                               delay 1    delay 2    delay 3
//
// ms ------1---------2---------3
// =============================O delay 1
// ===================O           delay 2
// =========O                     delay 3
//
// =============================O Promise.all

Ini berarti bahwa Promise.allakan menyelesaikan dengan data dari janji-janji batin setelah 3 detik.

Tetapi, Promise.allmemiliki perilaku "gagal cepat" :

Contoh # 2
const data = await Promise.all([res(3000), res(2000), rej(1000)])
//                              ^^^^^^^^^  ^^^^^^^^^  ^^^^^^^^^
//                               delay 1    delay 2    delay 3
//
// ms ------1---------2---------3
// =============================O delay 1
// ===================O           delay 2
// =========X                     delay 3
//
// =========X                     Promise.all

Jika Anda menggunakan async-awaitsebagai gantinya, Anda harus menunggu setiap janji untuk diselesaikan secara berurutan, yang mungkin tidak seefisien:

Contoh # 3
const delay1 = res(3000)
const delay2 = res(2000)
const delay3 = rej(1000)

const data1 = await delay1
const data2 = await delay2
const data3 = await delay3

// ms ------1---------2---------3
// =============================O delay 1
// ===================O           delay 2
// =========X                     delay 3
//
// =============================X await

zzzzBov
sumber
4
Jadi pada dasarnya perbedaannya hanyalah fitur "gagal cepat" dari Promise.all?
Matius
4
@ mclzc Dalam contoh # 3 eksekusi kode lebih lanjut dihentikan sampai delay1 diselesaikan. Itu bahkan dalam teks "Jika Anda menggunakan async-tunggu saja, Anda harus menunggu setiap janji untuk menyelesaikan secara berurutan"
haggis
1
@Qback, ada cuplikan kode langsung yang menunjukkan perilaku. Coba jalankan dan baca kembali kodenya. Anda bukan orang pertama yang salah paham tentang bagaimana urutan janji berperilaku. Kesalahan yang Anda buat dalam demo adalah Anda tidak memulai janji pada saat yang bersamaan.
zzzzBov
1
@zzzzBov Anda benar. Anda memulainya dalam waktu yang bersamaan. Maaf, saya datang ke pertanyaan ini karena alasan lain dan saya mengabaikannya.
Qback
2
" mungkin tidak seefisien " - dan yang lebih penting, menyebabkan unhandledrejectionkesalahan. Anda tidak akan pernah ingin menggunakan ini. Harap tambahkan ini ke jawaban Anda.
Bergi
87

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 UnhandledPromiseRejectionWarningdan PromiseRejectionHandledWarningmeskipun 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 thenjanji.

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 runfungsi dan kode dengan tangkap logika dalam panggilan balik ( gaya async ). Kita tidak perlu menangani kesalahan di dalam runfungsi karena fungsi async berfungsi secara otomatis - janji penolakan taskfungsi menyebabkan penolakan runfungsi. 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 awaitdi 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 meneleponrunfungsi 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 runasync

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 errdigunakan untuk memecat kesalahan terakhir)

mikep
sumber
14
jawaban ini lebih baik daripada jawaban yang diterima karena jawaban yang diterima saat ini melewatkan topik yang sangat penting dalam penanganan kesalahan
chrishiestand
8

Secara umum, menggunakan Promise.all()permintaan berjalan "async" secara paralel. Menggunakan awaitdapat berjalan secara paralel ATAU menjadi "sinkronisasi" pemblokiran.

fungsi test1 dan test2 di bawah ini menunjukkan cara awaitmenjalankan async atau sinkronisasi.

test3 menunjukkan Promise.all()itu async.

jsfiddle dengan hasil waktunya - buka konsol browser untuk melihat hasil tes

Perilaku sinkronisasi . TIDAK berjalan secara paralel, membutuhkan waktu ~ 1800 ms :

const test1 = async () => {
  const delay1 = await Promise.delay(600); //runs 1st
  const delay2 = await Promise.delay(600); //waits 600 for delay1 to run
  const delay3 = await Promise.delay(600); //waits 600 more for delay2 to run
};

Perilaku async . Berjalan secara paralel, membutuhkan ~ 600 ms :

const test2 = async () => {
  const delay1 = Promise.delay(600);
  const delay2 = Promise.delay(600);
  const delay3 = Promise.delay(600);
  const data1 = await delay1;
  const data2 = await delay2;
  const data3 = await delay3; //runs all delays simultaneously
}

Perilaku async . Berjalan secara paralel, membutuhkan ~ 600 ms :

const test3 = async () => {
  await Promise.all([
  Promise.delay(600), 
  Promise.delay(600), 
  Promise.delay(600)]); //runs all delays simultaneously
};

TLDR; Jika Anda menggunakannya Promise.all()juga akan "gagal cepat" - berhenti berjalan pada saat kegagalan pertama dari salah satu fungsi yang disertakan.

GavinBelson
sumber
1
Di mana saya bisa mendapatkan penjelasan terperinci tentang apa yang terjadi di bawah tudung di cuplikan 1 dan 2? Saya sangat terkejut bahwa ini memiliki cara yang berbeda untuk berlari karena saya mengharapkan perilaku yang sama.
Gregordy
2
@Regordy ya itu mengejutkan. Saya diposting jawaban ini untuk menyimpan coders baru untuk async sakit kepala. Ini semua tentang ketika JS mengevaluasi penantian, inilah mengapa Anda menetapkan variabel penting. Bacaan Async mendalam: blog.bitsrc.io/…
GavinBelson
7

Anda dapat memeriksa sendiri.

Dalam biola ini , saya menjalankan tes untuk menunjukkan sifat pemblokiran await, yang bertentangan dengan Promise.allyang akan memulai semua janji dan sementara satu menunggu itu akan berlanjut dengan yang lain.

zpr
sumber
6
Sebenarnya, biola Anda tidak menjawab pertanyaannya. Ada perbedaan antara menelepon t1 = task1(); t2 = task2()dan kemudian menggunakan awaitsetelahnya untuk keduanya result1 = await t1; result2 = await t2;seperti dalam pertanyaannya, sebagai lawan dari apa yang Anda uji yang menggunakan awaitpada panggilan asli seperti result1 = await task1(); result2 = await task2();. Kode dalam pertanyaannya memang memulai semua janji sekaligus. Perbedaannya, seperti yang ditunjukkan oleh jawabannya, adalah bahwa kegagalan akan dilaporkan lebih cepat dengan Promise.allcaranya.
BryanGrezeszak
Jawaban Anda di luar topik seperti @BryanGrezeszak berkomentar. Anda sebaiknya menghapusnya untuk menghindari pengguna yang menyesatkan.
mikep
0

Dalam hal menunggu Promise.all ([task1 (), task2 ()]); "task1 ()" dan "task2 ()" akan berjalan paralel dan akan menunggu sampai kedua janji selesai (baik diselesaikan atau ditolak). Sedangkan dalam hal

const result1 = await t1;
const result2 = await t2;

t2 hanya akan berjalan setelah t1 selesai dieksekusi (telah diselesaikan atau ditolak). Baik t1 dan t2 tidak akan berjalan paralel.

Waleed Naveed
sumber