Promise.all () और बहु ​​प्रतीक्षित के बीच कोई अंतर?


181

क्या इसमें कोई अंतर है:

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

तथा

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

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

तथा

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

जवाबों:


210

नोट :

यह उत्तर सिर्फ awaitश्रृंखला में और समय के बीच के अंतर को कवर करता है Promise.all@ Mikep के व्यापक उत्तर को पढ़ना सुनिश्चित करें जो त्रुटि से निपटने में अधिक महत्वपूर्ण अंतरों को भी कवर करता है


इस उत्तर के प्रयोजनों के लिए मैं कुछ उदाहरण विधियों का उपयोग करूंगा:

  • res(ms) एक ऐसा फ़ंक्शन है जो मिलीसेकंड का पूर्णांक लेता है और एक वादा करता है जो कई मिलीसेकंड के बाद हल होता है।
  • rej(ms) एक ऐसा कार्य है जो एक मिलीसेकंड का पूर्णांक लेता है और एक वादा करता है जो कई मिलीसेकंड के बाद खारिज कर देता है।

कॉलिंग resसे टाइमर शुरू होता है। का उपयोग करते हुए Promise.allदेरी के एक मुट्ठी भर के लिए प्रतीक्षा करने के बाद सभी देरी समाप्त कर दिया है हल करेंगे, लेकिन वे एक ही समय में निष्पादित याद रखें:

उदाहरण 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

इसका मतलब है कि Promise.all3 सेकंड के बाद आंतरिक वादों के डेटा के साथ हल होगा।

लेकिन, Promise.all"विफल तेज़" व्यवहार है :

उदाहरण # 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

यदि आप async-awaitइसके बजाय उपयोग करते हैं, तो आपको क्रमिक रूप से हल करने के लिए प्रत्येक वादे का इंतजार करना होगा, जो उतना कुशल नहीं हो सकता है:

उदाहरण # 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
तो मूल रूप से यह अंतर केवल Promise.all की "विफल तेज़" सुविधा है?
मैथ्यू

4
@mclzc उदाहरण # 3 में आगे कोड निष्पादन तब तक रुका हुआ है जब तक विलंब 1 हल नहीं हो जाता। यह पाठ में भी है "यदि आप इसके बजाय async-wait का उपयोग करते हैं, तो आपको क्रमिक रूप से हल करने के लिए प्रत्येक वादे का इंतजार करना होगा"
haggis

1
@ क्यूबैक, एक लाइव कोड स्निपेट है जो व्यवहार को प्रदर्शित करता है। इसे चलाने और कोड को फिर से पढ़ने पर विचार करें। आप यह समझने वाले पहले व्यक्ति नहीं हैं कि वादों का क्रम कैसे व्यवहार करता है। आपके द्वारा डेमो में की गई गलती यह है कि आप एक ही समय में अपने वादे शुरू नहीं कर रहे हैं।
zzzzBov

1
@zzzzBov तुम सही हो। आप इसे उसी समय शुरू कर रहे हैं। क्षमा करें, मुझे एक और कारण से यह सवाल आया और मैंने इसे नजरअंदाज कर दिया।
Qback

2
" यह उतना कुशल नहीं हो सकता है " - और अधिक महत्वपूर्ण बात, unhandledrejectionत्रुटियों का कारण । आप इसका उपयोग कभी नहीं करना चाहेंगे। कृपया इसे अपने उत्तर में जोड़ें।
बर्गी १४'१

88

पहला अंतर - तेजी से विफल

मैं @ 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 / प्रतीक्षा प्रॉमिस के लिए प्रतिस्थापन नहीं है। यह सिर्फ सुंदर तरीका है कि वादों का उपयोग कैसे किया जाता है ... एसिंक्स कोड सिंक शैली में लिखा गया है और हम 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!

जैसा कि आप सफलतापूर्वक त्रुटि को देखने के लिए देख सकते हैं कि हमें runफ़ंक्शन में केवल एक कैच जोड़ने की आवश्यकता है और कैच लॉजिक के साथ कोड कॉलबैक ( async शैली ) में है। हमें runफ़ंक्शन के अंदर हैंडल त्रुटियों की आवश्यकता नहीं है क्योंकि async फ़ंक्शन यह स्वचालित रूप से करता है - taskफ़ंक्शन की अस्वीकृति का कारण फ़ंक्शन की अस्वीकृति का कारण बनता है run। कॉलबैक से बचने के लिए हम सिंक स्टाइल (async / wait + try / catch) का उपयोग कर सकते हैं try { await run(); } catch(err) { }लेकिन इस उदाहरण में यह संभव नहीं है क्योंकि हम awaitमुख्य धागे में उपयोग नहीं कर सकते हैं - इसका उपयोग केवल async फ़ंक्शन में किया जा सकता है (यह तर्कसंगत है क्योंकि कोई भी नहीं चाहता है ब्लॉक मुख्य धागा)। यह देखने के लिए कि किसी अन्य async फ़ंक्शन से फ़ंक्शन में काम करना या IIFE का उपयोग करना (तुरंत इनवॉइस फ़ंक्शन अभिव्यक्ति) :। सिंक शैली हम कॉल कर सकते हैंrun(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)

... Wtf? हम पहले देखते हैं कि कार्य 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 ने अपने हटाए गए उत्तर में इस अजीब व्यवहार के बारे में पूछा, जो कि पकड़ा जा रहा है, लेकिन इसमें कोई त्रुटि भी है। हम त्रुटि पकड़ते हैं क्योंकि रन () प्रतीक्षित कीवर्ड के साथ लाइन पर अस्वीकार कर दिया जाता है और रन () कॉल करते समय कोशिश / पकड़ का उपयोग करके पकड़ा जा सकता है। हमें अनचाही त्रुटि भी मिलती है क्योंकि हम 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)

... "केवल" दो त्रुटियां (3 एक लापता है) लेकिन कुछ भी पकड़ा नहीं गया।


जोड़ (अलग-अलग कार्य त्रुटियों को और पहले-असफल त्रुटि)

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!

... ध्यान दें कि इस उदाहरण में मैंने नकारात्मक प्रदर्शन का इस्तेमाल किया = बेहतर प्रदर्शन के लिए दोनों कार्यों के लिए सच है कि क्या होता है ( throw errअंतिम त्रुटि को आग लगाने के लिए उपयोग किया जाता है)


14
यह उत्तर स्वीकृत उत्तर से बेहतर है क्योंकि वर्तमान में स्वीकार किया गया उत्तर त्रुटि से निपटने के बहुत महत्वपूर्ण विषय को याद करता है
chrishiestand

8

आम तौर पर, Promise.all()समानांतर में "एसिंक्स" के अनुरोधों का उपयोग करते हुए । उपयोग करना awaitसमानांतर में चल सकता है या "सिंक" अवरुद्ध हो सकता है।

नीचे test1 और test2 फ़ंक्शन दिखाते हैं कि कैसे awaitasync या सिंक चला सकते हैं।

test3 दिखाता है Promise.all()कि यह async है।

समय परिणामों के साथ jsfiddle - परीक्षण के परिणाम देखने के लिए ब्राउज़र कंसोल खोलें

सम्यक व्यवहार। समानांतर में नहीं चलता, ~ 1800ms लेता है :

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

Async व्यवहार। पैरेलल में चलता है , ~ 600ms लेता है :

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
}

Async व्यवहार। समानांतर में चलता है , ~ 600ms लेता है :

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

TLDR; यदि आप Promise.all()इसका उपयोग कर रहे हैं, तो यह "फास्ट-फेल" भी होगा - किसी भी शामिल फ़ंक्शन की पहली विफलता के समय चलना बंद कर दें।


1
मुझे स्निपेट्स 1 और 2 में हुड के नीचे क्या होता है, इसका विस्तृत विवरण मिल सकता है। मैं बहुत आश्चर्यचकित हूं कि इन्हें चलाने का एक अलग तरीका है क्योंकि मैं व्यवहारों के समान होने की उम्मीद कर रहा था।
ग्रेगॉर्डी

2
@Gregordy हाँ यह आश्चर्य की बात है। मैंने यह उत्तर कोडर नए को सहेजने के लिए पोस्ट किया है ताकि कुछ सिरदर्द हो सकें। यह सब तब है जब जेएस प्रतीक्षा का मूल्यांकन करता है, यही कारण है कि आप चर मामलों को कैसे असाइन करते हैं। गहराई में Async reading: blog.bitsrc.io/…
GavinBelson

7

आप अपने लिए जांच कर सकते हैं।

इस फिडेल में , मैंने अवरुद्ध प्रकृति को प्रदर्शित करने के लिए एक परीक्षण चलाया await, Promise.allजिसके विरोध में सभी वादे शुरू हो जाएंगे और जबकि एक इंतजार कर रहा है, वह दूसरों के साथ चलेगा।


6
वास्तव में, आपका फिडल उनके प्रश्न को संबोधित नहीं करता है। कॉलिंग t1 = task1(); t2 = task2()और फिर उसकेawait बाद दोनों के लिए result1 = await t1; result2 = await t2;उसके प्रश्न की तरह उपयोग करने के बीच एक अंतर है , जैसा कि आप परीक्षण कर रहे हैं जो awaitमूल कॉल पर उपयोग कर रहा है result1 = await task1(); result2 = await task2();। उनके प्रश्न में कोड एक ही बार में सभी वादे शुरू करता है। अंतर, जैसे कि पता चलता है, यह है कि विफलताओं को जल्दी से रिपोर्ट किया जाएगा Promise.all
ब्रायनग्रेज़्ज़ाक

आपका उत्तर ऑफ विषय है जैसे @BryanGrezeszak ने टिप्पणी की। उपयोगकर्ताओं को गुमराह करने से बचने के लिए आपको इसे हटा देना चाहिए।
माइक

0

वादे के मामले में Promise.all ([task1 (), task2 ()]); "task1 ()" और "task2 ()" समानांतर चलेगा और तब तक इंतजार करेगा जब तक कि दोनों वादे पूरे नहीं हो जाते (या तो हल हो गए या खारिज)। जबकि के मामले में

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

t1 केवल t1 के निष्पादन के समाप्त होने के बाद ही चलेगा (इसे हल या अस्वीकार कर दिया गया है)। T1 और t2 दोनों समानांतर नहीं चलेंगे।

हमारी साइट का प्रयोग करके, आप स्वीकार करते हैं कि आपने हमारी Cookie Policy और निजता नीति को पढ़ और समझा लिया है।
Licensed under cc by-sa 3.0 with attribution required.