Await Promise.all () ve birden çok await arasında herhangi bir fark var mı?


181

Arasında herhangi bir fark var mı:

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

ve

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

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

ve

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

Yanıtlar:


210

Not :

Bu cevap await, seri ve arasındaki zamanlama farklarını kapsamaktadır Promise.all. @ Mikep'in hata işlemedeki daha önemli farklılıkları da içeren kapsamlı cevabını mutlaka okuyun .


Bu cevap için bazı örnek yöntemler kullanacağım:

  • res(ms) milisaniyenin tamsayısını alan ve bu milisaniyeden sonra çözülen bir söz veren bir işlevdir.
  • rej(ms) milisaniyenin tamsayısını alan ve bu milisaniyeden sonra reddedilen bir söz veren bir işlevdir.

Arama reszamanlayıcıyı başlatır. Kullanılması Promise.alltüm gecikmeler bitirdikten sonra çözecektir gecikmeler bir avuç beklemek, ancak aynı zamanda yürütme hatırlamak:

Örnek 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

Bu, Promise.all3 saniye sonra iç vaatlerden alınan verilerle çözüleceği anlamına gelir .

Ancak, Promise.all"hızlı başarısız" davranışı vardır :

Örnek 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

Eğer kullanırsanız async-awaitbunun yerine, verimli olarak olmayabilir sırayla çözmek için her vaat, beklemek zorunda kalacak:

Örnek 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


4
Yani temelde fark Promise.all sadece "hızlı başarısız" özelliği?
Matthew

4
@mclzc Örnek 3'te, delay1 çözülene kadar başka kod yürütmesi durduruldu. "Onun yerine async-await kullanıyorsanız, her bir sözün sırayla çözülmesi için beklemeniz gerekecek" metninde bile var
haggis

1
@Qback, davranışı gösteren canlı bir kod snippet'i var. Çalıştırmayı ve kodu yeniden okumayı düşünün. Söz dizisinin nasıl davrandığını yanlış anlayan ilk kişi siz değilsiniz. Demonuzda yaptığınız hata, sözlerinize aynı anda başlamamanızdır.
zzzzBov

1
@zzzzBov Haklısın. Aynı zamanda başlıyorsunuz. Üzgünüm, başka bir nedenden dolayı bu soruya geldim ve bunu göz ardı ettim.
Geri

2
" o kadar verimli olmayabilir " - ve daha da önemlisi, unhandledrejectionhatalara neden olabilir . Bunu asla kullanmak istemeyeceksiniz. Lütfen cevabınıza ekleyin.
Bergi

88

İlk fark - hızlı başarısız

@ ZzzzBov'un cevabına katılıyorum ama Promise.all'ın "hızlı başarısız" avantajı sadece tek fark değil. Yorumlardaki bazı kullanıcılar, Promise.all dosyasını yalnızca negatif senaryoda daha hızlı olduğunda (bazı görevler başarısız olduğunda) neden kullanacaklarını sorar. Neden sormuyorum? İki bağımsız eşzamansız paralel görevim varsa ve birincisi çok uzun bir sürede çözülüyor ancak ikincisi çok kısa sürede reddediliyorsa, neden "çok kısa bir süre" yerine "çok uzun bir süre" yerine hata mesajını bekletmek? Gerçek hayattaki uygulamalarda olumsuz senaryoyu düşünmeliyiz. Ama Tamam - bu ilk farkta Promise.all ile çoklu alternatifin hangi alternatifi kullanacağınıza karar verebilirsiniz.

İkinci fark - hata işleme

Ancak hata işlemeyi düşünürken Promise.all kullanmalısınız. Birden fazla beklemeyle tetiklenen zaman uyumsuz paralel görevlerin hatalarını doğru işlemek mümkün değildir. Negatif senaryoda her zamanUnhandledPromiseRejectionWarning ve PromiseRejectionHandledWarningher yerde dene / yakala seçeneğini kullanırsınız. Bu nedenle Promise.all tasarlandı. Tabii ki birisi bu hataları kullanarak baskılayabileceğimizi söyleyebilir process.on('unhandledRejection', err => {})ve process.on('rejectionHandled', err => {})bu iyi bir uygulama değildir. İnternette, iki veya daha fazla bağımsız asenkron paralel görev için hata işlemeyi dikkate almayan veya hiç düşünmeyen ancak yanlış bir şekilde - sadece dene / yakala ve hata yakalamayı umarak kullanmayan birçok örnek buldum. İyi uygulama bulmak neredeyse imkansız. Bu yüzden bu cevabı yazıyorum.

özet

Asla iki veya daha fazla bağımsız zaman uyumsuz paralel görev için birden fazla bekleme kullanmayın, çünkü hataları ciddiye alamazsınız. Bu kullanım durumu için her zaman Promise.all () kullanın. Async / await, Promises'ın yerini almaz. Bu vaatleri kullanmak için güzel bir yol ... zaman uyumsuz kod yazılmıştır senkronize tarzda ve thenvaatlerde birden fazla önleyebilirsiniz .

Bazı insanlar Promise.all () kullanarak görev hatalarını ayrı ayrı ele alamayacağımızı ancak sadece ilk reddedilen vaatten kaynaklanan hatayı (evet, bazı kullanım durumlarının örneğin günlük kaydı için ayrı işlem gerektirebileceğini) söylüyorlar. Sorun değil - aşağıdaki "Ekleme" başlığına bakın.

Örnekler

Bu zaman uyumsuz görevi düşünün ...

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)
  });
};

Olumlu senaryoda görevleri çalıştırdığınızda Promise.all ve birden çok bekleme arasında bir fark yoktur. Her iki örnek de Task 1 succeed! Task 2 succeed!5 saniye sonra sona erer .

// 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!

Pozitif senaryoda ilk görev 10 saniye, negatif senaryoda ise görev 5 saniye sürdüğünde, verilen hatalarda farklılıklar vardır.

// 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!

Paralel olarak çoklu beklemeyi kullanırken yanlış bir şey yaptığımızı zaten fark etmeliyiz. Tabii ki hataları önlemek için biz halletmeliyiz! Hadi deneyelim...


// 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!

Hatayı başarılı bir şekilde ele alabileceğiniz gibi, runfonksiyona sadece bir catch eklememiz gerekiyor ve catch mantığı ile kod geri çağırmada ( async stili ). runİşlevin içindeki tutamaç hatalarına ihtiyacımız yoktur çünkü asenkron işlev otomatik olarak çalışır - taskişlevin reddedileceğine söz verin, işlevin reddedilmesine neden olur run. Geri aramadan kaçınmak için senkronizasyon stilini (async / await + try / catch) kullanabiliriz, try { await run(); } catch(err) { }ancak bu örnekte awaitana iş parçacığında kullanamadığımız için mümkün değildir - sadece async işlevinde kullanılabilir (mantıksaldır, çünkü kimse istemez ana ipliği engelle). İşlemin senkronize tarzda çalışıp çalışmadığını test etmek için arayabilirizrunBaşka bir zaman uyumsuz fonksiyonu ya da kullanım hayat (hemen kullanılan fonksiyonu ifade) gelen fonksiyonu: (async function() { try { await run(); } catch(err) { console.log('Caught error', err); }; })();.

Bu, iki veya daha fazla eşzamansız paralel görevi çalıştırmak ve hataları işlemek için doğru bir yoldur. Aşağıdaki örneklerden kaçınmalısınız.


// 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);
};

Kodu birkaç yolla ele almaya çalışabiliriz ...

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 

... hiçbir şey yakalanmadı çünkü senkronizasyon kodunu runişliyor ancak senkronize değil

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)

... O ne lan? Öncelikle görev 2'deki hatanın ele alınmadığını ve daha sonra yakalandığını görüyoruz. Yanıltıcı ve hala konsoldaki hatalarla dolu. Bu şekilde kullanılamaz.

(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)

... yukarıdaki ile aynı. User @Qwerty silinen cevabında yakalanmış gibi görünen bu tuhaf davranışı sordu ancak işlenmemiş hatalar da var. Run () anahtar kelimeyi bekleyen satırda reddedildiğinden ve run () çağrılırken try / catch kullanılarak yakalanabildiğinden hatayı yakalarız. Ayrıca, zaman uyumsuz görev işlevini eşzamanlı olarak çağırıyoruz (anahtar sözcüğü beklemeden) ve bu görev run () işlevinin dışında çalışıyor ve dışarıda da başarısız oluyor. SetTimeout'ta kodun hangi bölümünün çalıştığını bazı senkronizasyon işlevini çağırırken try / catch ile hata işleyemediğimizde benzerdir 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)

... "sadece" iki hata (3. hata eksik) ama hiçbir şey yakalanmadı.


Ekleme (görev hatalarını ayrı olarak ele alma ve ayrıca ilk başarısızlık hatası)

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!

... bu örnekte, ne olduğunu daha iyi göstermek için her iki görev için de negatifScenario = true kullandığımı unutmayın ( throw errson hatayı tetiklemek için kullanılır)


14
bu cevap kabul edilen cevaptan daha iyidir, çünkü şu anda kabul edilen cevap çok önemli hata yönetimi konusunu
kaçırmaktadır

8

Genellikle, Promise.all()paralel olarak "async" isteklerini çalıştırır. Kullanarak awaitparalel çalışabilir VEYA "senkronizasyon" engelleme olabilir.

Aşağıdaki test1 ve test2 fonksiyonları await, zaman uyumsuzluk veya senkronizasyonun nasıl çalıştırılabileceğini gösterir .

test3Promise.all() bunun zaman uyumsuz olduğunu gösterir .

zamanlanmış sonuçları olan jsfiddle - test sonuçlarını görmek için tarayıcı konsolunu açın

Senkronizasyon davranışı. Paralel olarak ÇALIŞMAZ , ~ 1800ms alır :

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
};

Zaman uyumsuz davranış. Paralel çalışır, ~ 600ms alır :

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
}

Zaman uyumsuz davranış. Paralel çalışır, ~ 600ms alır :

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

TLDR; Eğer kullanıyorsanız Promise.all()"hızlı arıza" - dahil edilen işlevlerden herhangi birinin ilk hatası sırasında çalışmayı durdurur .


1
Snippet 1 ve 2'de kaputun altında neler olduğuna dair ayrıntılı bir açıklama nereden alabilirim? Davranışların aynı olmasını beklediğimden, bunların farklı bir şekilde çalışması için çok şaşırdım.
Gregordy

2
@Gregordy evet şaşırtıcı. Bu cevabı, bazı baş ağrılarını asenkronize etmek için yeni kodlayıcıları kurtarmak için gönderdim. Her şey JS'nin beklemeyi değerlendirmesiyle ilgilidir, bu yüzden değişkenleri nasıl atadığınız önemlidir. Derinlemesine Async okuma: blog.bitsrc.io/…
GavinBelson

7

Kendiniz kontrol edebilirsiniz.

Bu kemanda , engelleme niteliğini göstermek için bir test yaptım.await aksine, Promise.allhangi vaatler tüm başlayacak ve bir bekliyor ederken diğerleri ile devam edecek.


6
Aslında, kemanınız onun sorusunu ele almıyor. Orijinal çağrıda kullandığınız testin aksine, her ikisi için daha sonra arama t1 = task1(); t2 = task2()ve ardından kullanma arasında bir fark vardır . Sorusundaki kod tüm vaatleri bir kerede başlatır. Yanıtın gösterdiği gibi fark, hataların yolla daha hızlı raporlanacağıdır . awaitresult1 = await t1; result2 = await t2;awaitresult1 = await task1(); result2 = await task2();Promise.all
BryanGrezeszak

Cevabınız @BryanGrezeszak gibi konu dışı yorum yaptı. Yanıltıcı kullanıcıları önlemek için silmeyi tercih etmelisiniz.
mikep

0

Durumunda bekliyoruz Promise.all ([Task1 () 2'den seçilir ()]); "task1 ()" ve "task2 ()" paralel olarak çalışır ve her iki sözün de tamamlanmasına (çözülene veya reddedilene) kadar bekler. Halbuki

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

t2 yalnızca t1 yürütmeyi tamamladıktan sonra (çözüldüğünde veya reddedildiğinde) çalışır. Hem t1 hem de t2 paralel çalışmaz.

Sitemizi kullandığınızda şunları okuyup anladığınızı kabul etmiş olursunuz: Çerez Politikası ve Gizlilik Politikası.
Licensed under cc by-sa 3.0 with attribution required.