Skip to content
This page has been auto-translated and may contain errors.View in English

असिंक्रोनस जावास्क्रिप्ट

docs.scrimba.com

कुछ चीजें तुरंत होती हैं: दो नंबरों को जोड़ना, एक वेरिएबल पढ़ना, पेज पर टेक्स्ट बदलना। दूसरी चीजों को समय लगता है। एक टाइमर तीन सेकंड के लिए काउंटडाउन करता है। एक सर्वर को भेजा गया अनुरोध नेटवर्क के पार जाता है और कुछ समय बाद वापस आता है। अगर जावास्क्रिप्ट रुकता और इनमें से हर एक के लिए प्रतीक्षा करता, तो पूरा पेज फ्रीज हो जाता: कोई क्लिक नहीं, कोई स्क्रोलिंग नहीं, कुछ भी नहीं, जब तक कि धीमी चीज खत्म न हो जाए। यह काम नहीं करता। यह अध्याय इस बारे में है कि जावास्क्रिप्ट कैसे कुछ धीमा शुरू करता है, पेज को प्रतिक्रियाशील रखता है, और नतीजा तैयार होने पर उसे संभालने के लिए वापस आता है।

असिंक्रोनस क्यों

अधिकांश जावास्क्रिप्ट ऊपर से नीचे तक चलता है, एक लाइन अगली को शुरू करने से पहले खत्म होती है। तेज़ काम के लिए यह ठीक है। लेकिन कुछ कार्यों को वास्तविक समय लगता है, और जावास्क्रिप्ट उनके लिए बैठकर प्रतीक्षा नहीं करता। यह धीमा कार्य शुरू करता है, अगली लाइन पर जाता है, और जब यह किया जाता है तो धीमी चीज पर वापस आता है। यही असिंक्रोनस का मतलब है: क्रम में नहीं होना, अभी नहीं, बल्कि बाद में, जब नतीजा तैयार हो।

सबसे सरल उदाहरण एक टाइमर है। setTimeout देरी के बाद कोड का एक टुकड़ा चलाता है:

js
console.log("Start");
setTimeout(() => {
  console.log("3 seconds later");
}, 3000);
console.log("End");
// Start
// End
// 3 seconds later

ऑर्डर पर ध्यान दें। "End" "3 seconds later" से पहले प्रिंट होता है, भले ही यह कोड में setTimeout के बाद आता है। जावास्क्रिप्ट उस लाइन पर तीन सेकंड तक प्रतीक्षा नहीं करता। इसने टाइमर सेट किया, आगे बढ़ा, और समय समाप्त होने पर विलंबित कोड चलाया।

जावास्क्रिप्ट एक बार में एक चीज चलाता है, ऊपर से नीचे तक। वह मॉडल तब तक काम करता है जब तक कोई कार्य समय लेता है: एक टाइमर, एक नेटवर्क अनुरोध, एक फाइल पढ़ना। अगर भाषा उन पर ब्लॉक करती, तो पेज फ्रीज हो जाता क्योंकि कुछ और नहीं चल सकता। असिंक्रोनस कोड उत्तर है: आप धीमा कार्य शुरू करते हैं, जावास्क्रिप्ट आपके बाकी कोड को चलाता रहता है, और नतीजा बाद में संभाला जाता है जब वह आता है।

setTimeout सबसे स्पष्ट पहला उदाहरण है। आप इसे एक फंक्शन और मिलीसेकंड में देरी देते हैं, और यह उस देरी के बाद उस फंक्शन को चलाता है:

js
console.log("Start");
setTimeout(() => {
  console.log("3 seconds later");
}, 3000);
console.log("End");
// Start
// End
// 3 seconds later

जो फंक्शन आप setTimeout को देते हैं वह एक callback है: कोड जो आप अभी नहीं, बाद में चलाने के लिए सौंपते हैं। जावास्क्रिप्ट टाइमर को रजिस्टर करता है, सीधे "End" पर जाता है, और केवल तभी कॉलबैक चलाता है जब देरी समाप्त हो जाती है और वर्तमान कोड खत्म हो जाता है। वह "अभी समाप्त करो, बाद में वापस आओ" पैटर्न पूरी async का विचार है, और इस अध्याय में बाकी सब कुछ इस पर बना हुआ है।

जावास्क्रिप्ट single-threaded है: एक कॉल स्टैक, एक बार में एक चीज का निष्पादन। वह constraint बिल्कुल यही है कि असिंक्रोनी क्यों महत्वपूर्ण है। एक नेटवर्क अनुरोध पर blocking wait एकल thread को stall कर देगा, और इसके साथ पेज पर हर क्लिक, animation, और render को भी। तो धीमा काम inline नहीं किया जाता। इसे runtime (ब्राउज़र या Node) को सौंपा जाता है, जो आपके thread के बाहर प्रतीक्षा करता है और आपके कोड को फिर से शुरू करने का शेड्यूल बनाता है जब नतीजा तैयार हो। असिंक्रोनस कोड वह hand-off है: आप वर्णन करते हैं कि एक ऐसे नतीजे के साथ क्या करना है जो आपके पास अभी नहीं है।

setTimeout न्यूनतम case है, और यह मॉडल को साफ़-साफ़ expose करता है:

js
console.log("Start");
setTimeout(() => {
  console.log("later");
}, 0);
console.log("End");
// Start
// End
// later

यहाँ delay 0 है, और कॉलबैक फिर भी आखिरी में चलता है। वह संकेत है: async callbacks कभी भी synchronous कोड के बीच में नहीं चलतेsetTimeout अपने कॉलबैक को zero milliseconds के बाद नहीं चलाता, यह कॉलबैक को current synchronous कोड के बाद चलने के लिए शेड्यूल करता है। उस ordering के पीछे का तंत्र event loop है, जो इस अध्याय के अंतिम खंड को अलग करता है। अभी के लिए, यह नियम रखें: synchronous कोड पूरी तरह चलता है, फिर queued async काम।

Junoअसिंक्रोनस क्यों कुछ कार्यों को समय लगता है, जैसे एक टाइमर या डेटा लोड करना, और जावास्क्रिप्ट प्रतीक्षा करते समय फ्रीज नहीं होता। यह धीमी चीज शुरू करता है, अगली लाइनें चलाता रहता है, और जब वह तैयार हो तो धीमी चीज पर वापस आता है। यही कारण है कि "End" कोड में टाइमर के पहले भी setTimeout संदेश से पहले प्रिंट होता है।
Junoअसिंक्रोनस क्यों Async का मतलब है अभी धीमा कार्य शुरू करो, बाद में इसके नतीजे को संभालो, और बीच में पेज को प्रतिक्रियाशील रखो। setTimeout सबसे सरल संस्करण है: आप इसे एक callback देते हैं और यह उस कॉलबैक को देरी के बाद चलाता है, तुरंत नहीं। एक बार जब आप देखते हैं कि विलंबित संदेश क्यों अंतिम में प्रिंट होता है, तो इस अध्याय का बाकी हिस्सा उसी एक विचार की भिन्नताएं हैं।
Junoअसिंक्रोनस क्यों जावास्क्रिप्ट single-threaded है, इसलिए धीमा काम blocking करने से सब कुछ stall हो जाता है। इसके बजाय आप wait को runtime को सौंपते हैं और वर्णन करते हैं कि जब नतीजा आता है तो उसके साथ क्या करना है। setTimeout(fn, 0) केस यह सच बताता है: कॉलबैक अभी भी synchronous कोड के बाद चलता है, क्योंकि queued async काम current कोड को पहले समाप्त करने की प्रतीक्षा करता है।

Promises

setTimeout एक delay के लिए ठीक है, लेकिन अधिकांश async काम एक value produce करता है जिसकी आप परवाह करते हैं: वह डेटा जो आपने मांगा था, या एक error अगर यह गलत हुआ। इसके लिए, जावास्क्रिप्ट एक promise का उपयोग करता है। एक promise एक value है जो अभी तैयार नहीं है, इसके लिए एक जगह सुरक्षित रखते हुए। यह एक receipt की तरह है: आपके पास खाना नहीं है, लेकिन आपके पास कुछ ऐसा है जो खाने में बदल जाएगा।

एक promise दो तरीकों में से एक समाप्त होता है। यह resolve होता है एक value के साथ जब काम सफल होता है, या reject होता है एक error के साथ जब यह विफल होता है। आप प्रत्येक case में .then() और .catch() के साथ बताते हैं कि क्या करना है:

js
loadUser()
  .then((user) => {
    console.log(`Loaded ${user.name}`);
  })
  .catch((error) => {
    console.log("Something went wrong");
  });

.then() चलता है जब promise resolve होता है, और यह value प्राप्त करता है। .catch() चलता है अगर यह reject होता है, और यह error प्राप्त करता है। उनके अंदर का कोड बाद में चलता है, एक बार नतीजा आने पर।

Callbacks काम करते हैं, लेकिन वे बुरी तरह stack करते हैं। जब एक async कार्य दूसरे पर निर्भर करता है, और वह दूसरे पर निर्भर करता है, तो आप callbacks को callbacks के अंदर नेस्ट करते हैं, हर कदम पर आगे की ओर बढ़ते हैं। उस आकार के पास एक नाम है, "pyramid of doom", और यह पढ़ना कठिन और error को संभालना मुश्किल हो जाता है। एक promise shape को ठीक करता है।

एक promise एक object है जो एक value के लिए खड़ा होता है जो अभी तैयार नहीं है। यह तीन states में से एक में होता है: pending (अभी काम चल रहा है), fulfilled (एक value के साथ resolve हो गया), या rejected (एक error के साथ विफल हो गया)। आप handlers को .then() सफलता के लिए और .catch() विफलता के लिए attach करते हैं:

js
loadUser()
  .then((user) => {
    console.log(`Loaded ${user.name}`);
    return loadPosts(user.id);
  })
  .then((posts) => {
    console.log(`Found ${posts.length} posts`);
  })
  .catch((error) => {
    console.log(`Failed: ${error.message}`);
  });

दो चीजें इसे nested callbacks से बेहतर बनाती हैं। पहला, यह flat है: प्रत्येक .then() एक promise return करता है, तो आप उन्हें सीधी लाइन में चेन करते हैं बजाय नेस्ट करने के। एक .then() से एक value return करें और अगला एक उसे प्राप्त करता है; एक promise return करें और chain उसके लिए प्रतीक्षा करता है। दूसरा, एक .catch() chain के किसी भी स्थान पर विफलता को संभालता है, इसलिए आप error handling को एक बार लिखते हैं बजाय हर level पर।

Promises से पहले, async results को callbacks के माध्यम से deliver किया जाता था, और dependent async steps का मतलब callbacks को callbacks के अंदर नेस्ट करना था। readability cost को छोड़कर, उस pattern के पास कोई unified error path नहीं है: हर level अपनी अपनी विफलता को handle करता है, और एक को भूल जाने से error silently निगल लिया जाता है। एक promise दोनों समस्याओं को solve करता है eventual result को एक first-class value बना कर जिसे आप pass around, chain, और एक जगह handle कर सकते हैं।

एक promise एक object है तीन states में से एक में: pending, एक value के साथ fulfilled, या एक reason के साथ rejected। एक बार यह settle हो जाता है (fulfilled या rejected) यह कभी फिर से नहीं बदलता। आप reactions को .then(onFulfilled) और .catch(onRejected) के साथ register करते हैं, और chaining वह जगह है जहाँ वास्तविक power है:

js
loadUser()
  .then((user) => loadPosts(user.id)) // एक promise return करें, chain इसके लिए प्रतीक्षा करता है
  .then((posts) => posts.filter((post) => post.published))
  .then((published) => console.log(`${published.length} published`))
  .catch((error) => console.log(`Failed: ${error.message}`));

प्रत्येक .then() एक नया promise return करता है, और handler की return value इसे determine करता है: एक plain value return करें और अगला .then() उस value को प्राप्त करता है, एक promise return करें और chain उसे adopt करता है और प्रतीक्षा करता है। कहीं भी एक thrown error या एक rejection बाकी .then() handlers को छोड़ता है और अगले .catch() पर जाता है, यही कारण है कि एक handler अंत में किसी भी step से विफलताओं को catch करता है। यह callbacks के ऊपर एक वास्तविक सुधार है, केवल सुंदर syntax नहीं: errors automatically propagate करते हैं, और flat chain उस क्रम में पढ़ता है जिसमें यह चलता है। व्यवहार में आप बहुत सारे promise chains को पढ़ेंगे, लेकिन आप अपने अपने ज्यादातर async कोड को async/await के साथ लिखेंगे, जो अगला खंड है और बिल्कुल इसी machinery पर बैठता है।

JunoPromises एक promise एक value है जो अभी तैयार नहीं है, एक result के लिए receipt की तरह जो बाद में आता है। यह एक value के साथ resolve होता है जब काम सफल होता है, या एक error के साथ reject होता है जब यह विफल होता है। आप .then() के साथ value के लिए और .catch() के साथ error के लिए बताते हैं कि क्या करना है, और दोनों बाद में चलते हैं एक बार नतीजा आने पर।
JunoPromises एक promise एक result के लिए खड़ा होता है जो अभी आ रहा है, और यह nested callbacks को beat करता है क्योंकि आप .then() को एक flat लाइन में chain करते हैं बजाय दाईं ओर बढ़ने के। एक .then() से एक value return करें और अगला एक उसे प्राप्त करता है, एक promise return करें और chain प्रतीक्षा करता है। एक .catch() chain के अंत में एक विफलता को किसी भी स्थान पर संभालता है, इसलिए आप error handling को एक बार लिखते हैं।
JunoPromises एक promise एक object है जो एक बार settle होता है, fulfilled या rejected के लिए, और कभी फिर से नहीं बदलता। प्रत्येक .then() एक नया promise return करता है, तो एक value return करना उसे pass करता है और एक promise return करना chain को wait करने देता है, जबकि कोई भी rejection अगले .catch() के आगे skip करता है। आप बहुत सारे chains को पढ़ेंगे, लेकिन अपने ज्यादातर async को async और await के साथ लिखेंगे, जो इसके बिल्कुल ऊपर बैठते हैं।

async / await

Promise chains काम करते हैं, लेकिन एक cleaner तरीका है उन्हें लिखने का जो लगभग सामान्य top-to-bottom कोड की तरह पढ़ता है। यह दो keywords का उपयोग करता है, async और await

आप एक फंक्शन को async चिह्नित करते हैं, और इसके अंदर आप एक promise को await कर सकते हैं। await फंक्शन को pause करता है जब तक promise resolve नहीं हो जाता, फिर आपको value सीधे देता है, कोई .then() की जरूरत नहीं:

js
async function showUser() {
  const user = await loadUser();
  console.log(`Loaded ${user.name}`);
}

उसे top से bottom तक पढ़ें: user को get करो, फिर name को log करो। await लाइन loadUser() को खत्म होने तक प्रतीक्षा करती है और आपको user देती है। फंक्शन वहाँ pause होता है, लेकिन बाकी पेज चलता रहता है, तो कुछ भी freeze नहीं होता।

Errors को handle करने के लिए, await को try और catch में wrap करें:

js
async function showUser() {
  try {
    const user = await loadUser();
    console.log(`Loaded ${user.name}`);
  } catch (error) {
    console.log("Could not load the user");
  }
}

अगर promise विफल होता है, कोड catch block में जाता है बजाय crash करने के।

async/await async कोड लिखने का आधुनिक तरीका है। यह promises पर built है, तो underlying में कुछ भी नहीं बदलता, लेकिन यह आपको async logic लिखने देता है जो functions से पहले से जानने वाले synchronous कोड की तरह पढ़ता है।

एक फंक्शन को async चिह्नित करें और आप इसके अंदर await का उपयोग कर सकते हैं। await एक promise लेता है, फंक्शन को तब तक pause करता है जब तक यह resolve न हो जाए, और resolved value के लिए evaluate करता है:

js
async function showUser() {
  const user = await loadUser();
  const posts = await loadPosts(user.id);
  console.log(`${user.name} has ${posts.length} posts`);
}

पिछले खंड से .then() chain की तुलना करें: एक ही काम, लेकिन flat और linear, values plain const bindings में उतरती हैं जिन्हें आप अगली लाइन पर उपयोग कर सकते हैं। एक async फंक्शन हमेशा एक promise return करता है, तो showUser() को कहीं और से await या .then() करने के लिए एक promise देता है।

Errors वह हिस्सा है जिसे लोग भूल जाते हैं। एक async फंक्शन के अंदर एक rejected promise throw करता है, तो आप इसे एक ordinary try/catch के साथ catch करते हैं:

js
async function showUser() {
  try {
    const user = await loadUser();
    console.log(`Loaded ${user.name}`);
  } catch (error) {
    console.log(`Failed: ${error.message}`);
  }
}

एक synchronous error के लिए जो try/catch आप करते हैं वह एक async एक को handle करता है, जो async/await के जीतने का एक बड़ा हिस्सा है।

async/await promises के ऊपर syntax है। एक async फंक्शन हमेशा एक promise return करता है, और await एक को unwrap करता है: यह फंक्शन को suspend करता है, runtime को thread वापस करता है, और resolved value के साथ resume करता है जब promise settle हो जाता है। कुछ भी blocked नहीं होता जबकि यह प्रतीक्षा करता है। फंक्शन pause होता है, लेकिन single thread दूसरा काम चलाने के लिए फ्री है, जो पूरा बिंदु है।

payoff है कि async कोड दोनों चीजें वापस पाता है जो callbacks और raw chains आपको cost करते हैं: linear reading order और normal control flow। await आपको const, conditionals, और loops को async values के चारों ओर उपयोग करने देता है जैसे कि वे synchronous हों:

js
async function publishReport(userId) {
  const user = await loadUser(userId);
  if (!user.active) {
    return null; // early return normally काम करता है
  }
  const posts = await loadPosts(user.id);
  return posts.filter((post) => post.published);
}

Error handling try/catch में collapse हो जाता है क्योंकि एक rejected awaited promise await पर throw करता है:

js
async function publishReport(userId) {
  try {
    const user = await loadUser(userId);
    return await loadPosts(user.id);
  } catch (error) {
    console.log(`Report failed: ${error.message}`);
    return [];
  }
}

एक subtlety worth internalizing: एक try के अंदर return await महत्वपूर्ण है। अगर आप return loadPosts(...) बिना await के लिखते हैं, फंक्शन promise return करता है और try को exit करता है इससे पहले कि promise settle हो, तो एक rejection catch को escape करता है। await के साथ, rejection thrown होता है जबकि आप अभी भी try के अंदर हैं, और catch इसे देखता है। यह उस तरह का विवरण है जो एक "मेरा error handler क्यों fire नहीं हुआ" bug में बदल जाता है, और event loop पर अगला खंड और error patterns इसे आगे जाता है।

Junoasync और await एक फंक्शन को async चिह्नित करें और आप इसके अंदर एक promise को await कर सकते हैं। await उस फंक्शन को तब तक pause करता है जब तक promise resolve न हो जाए और सीधे value को आपको देता है, तो कोड top से bottom तक कोई .then() के साथ नहीं पढ़ता। इसे try और catch में wrap करें failures को handle करने के लिए, और पेज चलता रहता है जबकि फंक्शन प्रतीक्षा करता है।
Junoasync और awaitasync/await cleaner syntax के साथ promises है: await फंक्शन को तब तक pause करता है जब तक एक promise resolve न हो जाए और आपको एक plain const में value देता है। एक async फंक्शन हमेशा एक promise return करता है, तो आप इसे कहीं और से await कर सकते हैं। Errors throw करते हैं, तो एक normal try/catch उन्हें handle करता है, और यह सबसे ज्यादा कारण है कि यह style जीत गया।
Junoasync और awaitawait फंक्शन को suspend करता है और thread को yield करता है, फिर resolved value के साथ resume करता है, तो कुछ भी block नहीं होता जबकि आप प्रतीक्षा करते हैं। आपको linear reading, normal control flow, और errors के लिए try/catch मिलता है। try के अंदर return await को watch करें: await को drop करें और एक rejection catch को देखने से पहले escape करता है।

डेटा को फेच करना

सबसे आम async task जो आप लिखेंगे वह एक सर्वर से डेटा लोड करना है। ब्राउज़र इसके लिए आपको fetch देता है। आप इसे एक URL pass करते हैं, और यह response के लिए एक promise return करता है:

js
async function loadUsers() {
  const response = await fetch("https://api.example.com/users");
  const users = await response.json();
  console.log(users);
}

दो await हैं, और यह लोगों को पहली बार आश्चर्यचकित करता है। पहला server के respond करने के लिए प्रतीक्षा करता है। दूसरा उस response का body पढ़ता है और इसे .json() के साथ जावास्क्रिप्ट डेटा में बदलता है, जो अपने आप में async है। तो fetching दो steps हैं: response को get करो, फिर उसे read करो।

Servers एक error के साथ भी respond कर सकते हैं, जैसे "not found"। fetch अपने आप पर उसे एक विफलता के रूप में नहीं treat करता, तो आप response.ok को अपने आप check करते हैं:

js
async function loadUsers() {
  const response = await fetch("https://api.example.com/users");
  if (!response.ok) {
    console.log("Request failed");
    return;
  }
  const users = await response.json();
  console.log(users);
}

एक सर्वर से डेटा लोड करना async task है जिसके लिए आप सबसे ज्यादा पहुँचेंगे, और ब्राउज़र का fetch यही है कि आप इसे कैसे करते हैं। एक URL दें और यह एक Response object के लिए एक promise return करता है:

js
async function loadUsers() {
  const response = await fetch("https://api.example.com/users");
  if (!response.ok) {
    throw new Error(`Request failed: ${response.status}`);
  }
  const users = await response.json();
  return users;
}

दो-step nature वह चीज है जो आप रखते हैं। पहला await resolve होता है जब response headers आते हैं, आपको एक Response देते हैं। वह response अभी डेटा नहीं है, यह इसके लिए एक handle है। body को read करना एक दूसरा async step है: response.json() एक promise return करता है जो parsed data के लिए resolve होता है। दो awaits, दो stages।

gotcha जो सब को catch करता है: fetch केवल एक network failure पर reject करता है, एक HTTP error status पर नहीं। एक 404 या 500 अभी भी resolve होता है; response.ok को अपने आप check करें। response.ok 200s में status codes के लिए true है, तो एक guard !response.ok पर एक बुरे status को एक वास्तविक error में बदलना है। एक बार आप वहाँ throw करते हैं, call के चारों ओर एक try/catch दोनों network failures और बुरे responses को एक जगह handle करता है।

fetch(url) ब्राउज़र का async HTTP client है, और यह एक Response के लिए एक promise return करता है। design के पास एक शार्प edge है जो आगे बताने के लिए worth है: promise केवल एक network-level failure पर reject करता है, एक dropped connection या एक blocked request पर। एक HTTP error status जैसे 404 या 500 एक successful round trip है जहाँ तक fetch concerned है, तो यह normally resolve करता है। response.ok को check करना optional नहीं है।

js
async function loadUsers() {
  const response = await fetch("https://api.example.com/users");
  if (!response.ok) {
    throw new Error(`Request failed: ${response.status}`);
  }
  return await response.json();
}

दो-stage shape streaming को reflect करता है। पहला await settle होता है जब response headers आते हैं, body के जरूरी आने से पहले। Response एक stream को एक handle है, और response.json() उस stream को completion तक read करता है और उसे parse करता है, जो यही कारण है कि यह अपने आप में async है और अपने आप await की जरूरत है। एक बुरे status को फंक्शन के अंदर एक thrown error में बदलना मतलब है एक try/catch call site पर तीन अलग-अलग failure modes को cover करता है: network failure, बुरा status, और JSON जो parse करने में विफल होता है, एक handler पर routed:

js
try {
  const users = await loadUsers();
  render(users);
} catch (error) {
  console.log(`Could not load users: ${error.message}`);
}

यह fetch का पूरा practical anatomy है: response के लिए एक promise, एक status check, body के लिए एक दूसरा promise, और सब चारों ओर एक single error boundary। अगला खंड covers करता है कि क्या होता है जब आपको इनमें से कई एक साथ की जरूरत होती है।

Junoडेटा को फेच करनाfetch(url) सर्वर के response के लिए एक promise return करता है, और डेटा लोड करना दो steps हैं: response के लिए await fetch(...), फिर await response.json() को डेटा को इसके से read करने के लिए। एक सर्वर error जैसे "not found" अपने आप fail नहीं होता, तो body को read करने से पहले response.ok को check करें। दोनों steps await का उपयोग करते हैं क्योंकि दोनों को समय लगता है।
Junoडेटा को फेच करनाfetch एक Response के लिए एक promise return करता है, और body को response.json() से read करना एक दूसरा async step है, तो एक normal fetch के दो awaits हैं। trap: fetch केवल एक network failure पर reject करता है, तो एक 404 या 500 अभी भी resolve होता है। response.ok को check करें और एक बुरे status पर throw करें, और एक try/catch सब कुछ cover करता है।
Junoडेटा को फेच करनाfetch resolve होता है जब headers आते हैं, body नहीं, जो यही कारण है कि response.json() एक अलग awaited step है जो stream को read करता है। यह केवल एक network failure पर reject करता है, तो एक 404 fine में resolve होता है और आपको अपने आप response.ok को check करना चाहिए। एक बुरे status पर throw करें और एक try/catch call site पर network errors, बुरे statuses, और parse failures को एक साथ handle करता है।

Event loop और parallel में काम चलाना

अब तक सब कुछ एक theme रखता है: जावास्क्रिप्ट धीमा काम शुरू करता है, आगे बढ़ता है, और बाद में इसमें वापस आता है। यह खंड उसके पीछे का तंत्र है, साथ ही कई async tasks को अच्छी तरह से चलाने के लिए patterns हैं।

यहाँ एक picture है कि जावास्क्रिप्ट कैसे async काम के ट्रैक को रखता है। आपका running कोड call stack पर बैठता है, अभी जो execute हो रहा है उसकी list है। जब आप setTimeout को call करते हैं, timer को ब्राउज़र को hand off किया जाता है countdown करने के लिए, तो यह stack पर नहीं है कुछ भी block करते हुए। जब timer खत्म हो जाता है, इसका callback एक task queue में जाता है, code की एक waiting line जो चलने के लिए तैयार है।

event loop वह हिस्सा है जो उन्हें connect करता है। इसका एक rule है: पहले call stack पर सब कुछ चलाएं, और केवल जब stack empty हो तो queue से अगला task pull करें। यही कारण है कि setTimeout(fn, 0) अभी भी आपके दूसरे कोड के बाद चलता है, एक zero delay के साथ भी। callback को queue में wait करना होता है current code के खत्म होने तक:

js
console.log("first");
setTimeout(() => console.log("third"), 0);
console.log("second");
// first
// second
// third

0 का delay मतलब "right now" नहीं है। यह मतलब है "जैसे ही current code खत्म हो"।

event loop वह engine है जो इस सब ordering को काम करने देता है। तीन parts: call stack (synchronous कोड right now चल रहा है), task queue (async callbacks उनका turn प्रतीक्षा कर रहे हैं), और loop अपने आप, जो एक चीज करता है: जब call stack empty है, queue से अगला task लें और इसे चलाएं।

वह single rule setTimeout(fn, 0) ordering को explain करता है। delay एक minimum wait है callback को queued होने से पहले, तुरंत चलने का एक promise नहीं, और एक queued task तब तक नहीं चल सकता जब तक current synchronous कोड खत्म न हो और stack को clear न करे:

js
console.log("first");
setTimeout(() => console.log("third"), 0);
console.log("second");
// first
// second
// third

जब आपके पास कई independent async tasks हैं, उन्हें एक के बाद एक चलाना समय को waste करता है। एक loop में await करना common mistake है:

js
// Slow: हर request पिछले को खत्म होने के लिए प्रतीक्षा करता है
const users = [];
for (const id of ids) {
  users.push(await loadUser(id));
}

अगर tasks एक दूसरे पर depend नहीं करते, उन्हें एक साथ शुरू करें और Promise.all के साथ एक साथ सब के लिए प्रतीक्षा करें। यह promises की एक array लेता है और उनके results की एक array के लिए एक promise return करता है:

js
// Fast: सब requests एक ही समय पर चलते हैं
const users = await Promise.all(ids.map((id) => loadUser(id)));

Promise.all के लिए reach करें जब भी आपके पास independent async काम है, और sequential await को एक loop में रखें केवल जब हर step को सच में पिछले के result की जरूरत हो।

event loop वह scheduler है जो single-threaded async को संभव बनाता है। call stack right now execute होने वाले synchronous frames को रखता है। Async callbacks directly इसे पर push नहीं करते; वे एक queue में प्रतीक्षा करते हैं। Loop का rule सरल है: जब call stack empty है, अगला task को dequeue करें और इसे completion तक चलाएं, फिर repeat करें। एक task uninterrupted चलता है, जो यही कारण है कि long synchronous काम अभी सब कुछ को block करता है, async या नहीं।

वह model setTimeout(fn, 0) की पूरी explanation है। delay को queue में एक minimum time के बाद callback को schedule करता है, लेकिन यह तब तक नहीं चल सकता जब तक stack drain न हो, तो यह हमेशा synchronous कोड को follow करता है:

js
console.log("first");
setTimeout(() => console.log("third"), 0);
console.log("second");
// first, second, third

एक refinement अपने head में रखने के लिए: promise callbacks (.then handlers और everything एक await के बाद) एक separate, higher-priority microtask queue पर जाते हैं जो tasks के बीच completely drain होता है, तो एक resolved promise का continuation एक queued setTimeout से पहले चलता है। आपको शायद ही कभी इसके बारे में reason करना पड़ता है, लेकिन यह occasional surprise को explain करता है जहाँ promise code एक timer को beat करता है।

practical payoff आपने अपने काम को अच्छी तरह से schedule करना है। Independent async tasks को parallel में चलना चाहिए, series में नहीं। एक loop में await करना उन्हें serialize करता है:

js
// Serial: total time हर request का sum है
for (const id of ids) {
  results.push(await loadUser(id));
}

// Parallel: total time roughly सबसे धीमा single request है
const results = await Promise.all(ids.map((id) => loadUser(id)));

Promise.all हर promise को तुरंत शुरू करता है (.map उन सब को kick off करता है), फिर एक बार सब settle हो जाते हैं resolve होता है, कुछ भी अगर कोई भी reject करता है। इसे independent काम के लिए उपयोग करें; sequential await को रखें केवल जब एक step को पिछले result की जरूरत हो। race conditions पर एक caution: अगर दो async operations एक ही state में write करते हैं और आप उनके order को control नहीं करते, अंतिम जो finish होता है वह जीतता है, जो अंतिम हो सकता है जो आपने शुरू किया। जब order महत्वपूर्ण हो, अगला शुरू करने से पहले पहले को await करें, या हर result को key करें तो एक late arrival एक नए को overwrite नहीं कर सकता।

JunoEvent loop आपका running कोड call stack पर बैठता है, और जब एक timer खत्म हो जाता है इसका callback task queue में प्रतीक्षा करता है। event loop सब कुछ stack पर पहले चलाता है, फिर अगला waiting task pull करता है, जो यही कारण है कि setTimeout(fn, 0) अभी भी आपने बाकी कोड के बाद चलता है। 0 की delay मतलब है "right now" नहीं, बल्कि "जैसे ही current code खत्म हो"।
JunoEvent loop event loop call stack को empty में चलाता है, फिर queue से अगला task लेता है, जो बिल्कुल यही कारण है कि setTimeout(fn, 0) आपने synchronous कोड को खत्म होने के लिए प्रतीक्षा करता है। जब async tasks एक दूसरे पर depend नहीं करते, उन्हें एक लूप में एक के बाद एक await मत करो। उन्हें एक साथ शुरू करो और Promise.all का उपयोग करो, और sequential await को रखो केवल जब एक step को अंतिम के result की जरूरत हो।
JunoEvent loop loop एक task को completion में चलाता है, फिर अगला, तो long synchronous काम सब कुछ को block करता है और setTimeout(fn, 0) हमेशा current code को trail करता है। Promise continuations एक higher-priority microtask queue पर tasks के बीच drain होते हैं, जो odd time को explain करता है कि .then एक timer को beat करता है। independent काम को Promise.all के साथ चलाओ बजाय एक loop में await करने के, और जब दो operations एक ही state को touch करते हैं, order को control करो या एक late finish एक नए result को overwrite करता है।

Async यहाँ से कहाँ जाता है

Async जावास्क्रिप्ट का वह हिस्सा है जो एक पेज को जीवंत रखता है जबकि धीमा काम background में होता है। shape हमेशा एक जैसा है: कुछ ऐसा शुरू करो जो समय लेता है, अपने बाकी कोड को चलने दो, और नतीजे को handle करो जब यह आता है। Promises उस नतीजे को एक value देता है जिसे आप pass around कर सकते हैं, async/await आपको plain top-to-bottom कोड में इसे लिखने देता है, और fetch है जहाँ आप सब का उपयोग सबसे ज्यादा करेंगे, एक सर्वर से डेटा लोड करते हुए।

यहाँ से, async language के उन हिस्सों से connects करता है जो बाहरी दुनिया को react करते हैं। आप fetched डेटा को the DOM के माध्यम से पेज में wire करेंगे, और आप async काम को kick off करेंगे जवाब में जो लोग पेज पर करते हैं, जो events है। नतीजे जो आप वापस पाते हैं वह लगभग हमेशा objects हैं, तो उन्हें read और reshape करना अगली skill है जो यहाँ सब के साथ pairs करती है।