एसिंक्रोनस फ़ंक्शन: वादों को आसान तरीके से पेश करना

असाइन किए गए फ़ंक्शन की मदद से, प्रॉमिस पर आधारित कोड को सिंक्रोनस तरीके से लिखा जा सकता है.

Chrome, Edge, Firefox, और Safari में डिफ़ॉल्ट रूप से, एक साथ कई काम करने की सुविधा चालू होती है. यह सुविधा काफ़ी बेहतरीन है. इनकी मदद से, प्रोमिस पर आधारित कोड को वैसे ही लिखा जा सकता है जैसे कि वह सिंक्रोनस हो. हालांकि, ऐसा करने पर मुख्य थ्रेड ब्लॉक नहीं होता. इनसे आपके असाइनिश्नल कोड को कम "चतुर" और ज़्यादा आसानी से पढ़ा जा सकता है.

असाइन किए गए फ़ंक्शन इस तरह काम करते हैं:

async function myFirstAsyncFunction() {
  try {
    const fulfilledValue = await promise;
  } catch (rejectedValue) {
    // …
  }
}

अगर फ़ंक्शन की परिभाषा से पहले async कीवर्ड का इस्तेमाल किया जाता है, तो फ़ंक्शन में await का इस्तेमाल किया जा सकता है. किसी प्रॉमिस को await करने पर, फ़ंक्शन को तब तक रोक दिया जाता है, जब तक प्रॉमिस पूरा नहीं हो जाता. अगर वादा पूरा हो जाता है, तो आपको पैसे वापस मिल जाते हैं. अगर प्रॉमिस अस्वीकार हो जाता है, तो अस्वीकार की गई वैल्यू को थ्रो किया जाता है.

ब्राउज़र समर्थन

ब्राउज़र के इस्तेमाल से जुड़ी सहायता

  • Chrome: 55.
  • Edge: 15.
  • Firefox: 52.
  • Safari: 10.1.

सोर्स

उदाहरण: फ़ेच को लॉग करना

मान लें कि आपको कोई यूआरएल फ़ेच करना है और जवाब को टेक्स्ट के तौर पर लॉग करना है. प्रोमिस का इस्तेमाल करके, यह इस तरह दिखता है:

function logFetch(url) {
  return fetch(url)
    .then((response) => response.text())
    .then((text) => {
      console.log(text);
    })
    .catch((err) => {
      console.error('fetch failed', err);
    });
}

यहां असिंक्रोनस फ़ंक्शन का इस्तेमाल करके, वही काम किया गया है:

async function logFetch(url) {
  try {
    const response = await fetch(url);
    console.log(await response.text());
  } catch (err) {
    console.log('fetch failed', err);
  }
}

लाइनों की संख्या पहले जैसी ही है, लेकिन सभी कॉलबैक हटा दिए गए हैं. इससे, इसे पढ़ना काफ़ी आसान हो जाता है. खास तौर पर, उन लोगों के लिए जिन्हें वादे के बारे में कम जानकारी है.

एक साथ काम नहीं करने वाली रिटर्न वैल्यू

ऐसाइन किए गए फ़ंक्शन, await का इस्तेमाल किए जाने या न किए जाने के बावजूद, हमेशा एक प्रॉमिस दिखाते हैं. यह प्रोमिस, ऐसाइन किए गए असाइनिक फ़ंक्शन से मिलने वाले नतीजे के साथ पूरा होता है या असाइन किए गए असाइनिक फ़ंक्शन से मिलने वाले गड़बड़ी के मैसेज के साथ अस्वीकार होता है. इसलिए, इनके साथ:

// wait ms milliseconds
function wait(ms) {
  return new Promise((r) => setTimeout(r, ms));
}

async function hello() {
  await wait(500);
  return 'world';
}

hello() को कॉल करने पर, एक प्रॉमिस मिलता है, जो "world" के साथ पूरा होता है.

async function foo() {
  await wait(500);
  throw Error('bar');
}

foo() को कॉल करने पर, एक प्रॉमिस मिलता है, जो Error('bar') के साथ अस्वीकार करता है.

उदाहरण: जवाब स्ट्रीम करना

जटिल उदाहरणों में, असाइन किए गए फ़ंक्शन के फ़ायदे बढ़ जाते हैं. मान लें कि आपको चंक को लॉग आउट करते समय जवाब स्ट्रीम करना है और आखिरी साइज़ दिखाना है.

यहां यह वादे के साथ दिया गया है:

function getResponseSize(url) {
  return fetch(url).then((response) => {
    const reader = response.body.getReader();
    let total = 0;

    return reader.read().then(function processResult(result) {
      if (result.done) return total;

      const value = result.value;
      total += value.length;
      console.log('Received chunk', value);

      return reader.read().then(processResult);
    });
  });
}

मुझे देखें, जैक "वादे करने वाला" आर्किबाल्ड. देखें कि असाइनॉन्स लूप सेट अप करने के लिए, मैंने processResult() को खुद में कैसे कॉल किया है? ऐसा लेख जिससे मुझे बहुत स्मार्ट महसूस हुआ. हालांकि, ज़्यादातर "स्मार्ट" कोड की तरह, आपको इसे देखने के लिए बहुत समय लगेगा, ताकि यह पता चल सके कि यह क्या कर रहा है. यह 90 के दशक की मैजिक-आई इमेज की तरह है.

आइए, अब असाइन किए गए फ़ंक्शन के साथ फिर से कोशिश करते हैं:

async function getResponseSize(url) {
  const response = await fetch(url);
  const reader = response.body.getReader();
  let result = await reader.read();
  let total = 0;

  while (!result.done) {
    const value = result.value;
    total += value.length;
    console.log('Received chunk', value);
    // get the next result
    result = await reader.read();
  }

  return total;
}

"स्मार्ट" सुविधाएं अब नहीं हैं. असाइनॉन्स लूप की जगह, भरोसेमंद और उबाऊ while-loop का इस्तेमाल किया गया है. बहुत बेहतर. आने वाले समय में, आपको ऐसाइन किए गए फ़ंक्शन के साथ काम करने वाले आइटरेटर मिलेंगे. ये while लूप को for-of लूप से बदल देंगे, जिससे यह और भी बेहतर हो जाएगा.

अन्य असाइनिक फ़ंक्शन सिंटैक्स

हमने आपको async function() {} पहले ही दिखा दिया है, लेकिन async कीवर्ड का इस्तेमाल, दूसरे फ़ंक्शन सिंटैक्स के साथ किया जा सकता है:

ऐरो फ़ंक्शन

// map some URLs to json-promises
const jsonPromises = urls.map(async (url) => {
  const response = await fetch(url);
  return response.json();
});

ऑब्जेक्ट के तरीके

const storage = {
  async getAvatar(name) {
    const cache = await caches.open('avatars');
    return cache.match(`/avatars/${name}.jpg`);
  }
};

storage.getAvatar('jaffathecake').then();

क्लास के तरीके

class Storage {
  constructor() {
    this.cachePromise = caches.open('avatars');
  }

  async getAvatar(name) {
    const cache = await this.cachePromise;
    return cache.match(`/avatars/${name}.jpg`);
  }
}

const storage = new Storage();
storage.getAvatar('jaffathecake').then();

सावधान! क्रम से वीडियो बनाने से बचें

भले ही, आपने सिंक्रोनस तरीके से काम करने वाला कोड लिखा हो, लेकिन पक्का करें कि आपने एक साथ कई काम करने का मौका न छोड़ा हो.

async function series() {
  await wait(500); // Wait 500ms…
  await wait(500); // …then wait another 500ms.
  return 'done!';
}

ऊपर दिए गए फ़ंक्शन को पूरा होने में 1000 मिलीसेकंड लगते हैं, जबकि:

async function parallel() {
  const wait1 = wait(500); // Start a 500ms timer asynchronously…
  const wait2 = wait(500); // …meaning this timer happens in parallel.
  await Promise.all([wait1, wait2]); // Wait for both timers in parallel.
  return 'done!';
}

ऊपर दिए गए कोड को पूरा होने में 500 मिलीसेकंड लगते हैं, क्योंकि दोनों इंतज़ार एक ही समय पर होते हैं. आइए, एक उदाहरण देखते हैं.

उदाहरण: फ़ेच किए गए डेटा को क्रम से आउटपुट करना

मान लें कि आपको यूआरएल की एक सीरीज़ फ़ेच करनी है और उन्हें सही क्रम में, जल्द से जल्द लॉग करना है.

गहरी सांस लें - वादे के साथ यह ऐसा दिखता है:

function markHandled(promise) {
  promise.catch(() => {});
  return promise;
}

function logInOrder(urls) {
  // fetch all the URLs
  const textPromises = urls.map((url) => {
    return markHandled(fetch(url).then((response) => response.text()));
  });

  // log them in order
  return textPromises.reduce((chain, textPromise) => {
    return chain.then(() => textPromise).then((text) => console.log(text));
  }, Promise.resolve());
}

हां, यह सही है. मैं reduce का इस्तेमाल करके, एक से ज़्यादा वादे करने के लिए चेन बना रहा/रही हूं. मैं बहुत चतुर हूं. हालांकि, यह बहुत स्मार्ट कोडिंग है, जिसे इस्तेमाल करने से बचना चाहिए.

हालांकि, ऊपर दिए गए फ़ंक्शन को असाइन फ़ंक्शन में बदलते समय, बहुत क्रम से काम करने का मन करता है:

सुझाया नहीं जाता - बहुत क्रम से
async function logInOrder(urls) {
  for (const url of urls) {
    const response = await fetch(url);
    console.log(await response.text());
  }
}
यह तरीका ज़्यादा बेहतर लगता है. हालांकि, मेरा दूसरा फ़ेच तब तक शुरू नहीं होता, जब तक मेरा पहला फ़ेच पूरी तरह से पढ़ा नहीं जाता. यह, एक साथ फ़ेच करने वाले प्रॉमिस के उदाहरण के मुकाबले काफ़ी धीमा है. शुक्र है कि इसके बीच में एक सही विकल्प है.
सुझाया गया - अच्छा और पैरलल
function markHandled(...promises) {
  Promise.allSettled(promises);
}

async function logInOrder(urls) {
  // fetch all the URLs in parallel
  const textPromises = urls.map(async (url) => {
    const response = await fetch(url);
    return response.text();
  });

  markHandled(...textPromises);

  // log them in sequence
  for (const textPromise of textPromises) {
    console.log(await textPromise);
  }
}
इस उदाहरण में, यूआरएल एक साथ फ़ेच और पढ़े जाते हैं. हालांकि, "स्मार्ट" reduce बिट को स्टैंडर्ड, बोरिंग, और पढ़ने लायक for-loop से बदल दिया गया है.

ब्राउज़र के लिए सहायता पाने का तरीका: जनरेटर

अगर ऐसे ब्राउज़र टारगेट किए जा रहे हैं जिनमें जनरेटर काम करते हैं, तो हर बड़े ब्राउज़र के नए वर्शन में, असाइन किए गए फ़ंक्शन को polyfill किया जा सकता है.

Babel यह काम आपके लिए करेगा, यहां Babel REPL का इस्तेमाल करके इसका उदाहरण दिया गया है

  • ध्यान दें कि ट्रांसपाइल किया गया कोड कितना मिलता-जुलता है. यह बदलाव, Babel के es2017 प्रीसेट का हिस्सा है.

हमारा सुझाव है कि आप ट्रांसपाइल करने के तरीके का इस्तेमाल करें. ऐसा इसलिए, क्योंकि टारगेट ब्राउज़र में असाइनिक फ़ंक्शन काम करने के बाद, इसे बंद किया जा सकता है. हालांकि, अगर आपको वाकई ट्रांसपाइलर का इस्तेमाल नहीं करना है, तो Babel के पॉलीफ़िल का इस्तेमाल करें. इसके बजाय:

async function slowEcho(val) {
  await wait(1000);
  return val;
}

…आपको polyfill शामिल करना होगा और यह लिखना होगा:

const slowEcho = createAsyncFunction(function* (val) {
  yield wait(1000);
  return val;
});

ध्यान दें कि आपको createAsyncFunction को जनरेटर (function*) पास करना होगा और await के बजाय yield का इस्तेमाल करना होगा. इसके अलावा, यह पहले की तरह ही काम करता है.

समस्या हल करने का तरीका: regenerator

अगर पुराने ब्राउज़र को टारगेट किया जा रहा है, तो Babel जनरेटर को भी ट्रांसपाइल कर सकता है. इससे, IE8 तक के सभी ब्राउज़र पर असाइनिक फ़ंक्शन का इस्तेमाल किया जा सकता है. इसके लिए, आपके पास Babel का es2017 प्रीसेट और es2015 प्रीसेट होना चाहिए.

आउटपुट उतना अच्छा नहीं होता, इसलिए कोड में ग़ैर-ज़रूरी चीज़ों का इस्तेमाल न करें.

सभी चीज़ों को एक साथ काम न करने वाले टास्क के तौर पर सेट करें!

जब सभी ब्राउज़र पर असाइन किए गए फ़ंक्शन काम करने लगेंगे, तो उनका इस्तेमाल हर उस फ़ंक्शन पर करें जो प्रोमिस दिखाता है! ये आपके कोड को बेहतर बनाते हैं. साथ ही, यह पक्का करते हैं कि फ़ंक्शन हमेशा एक प्रॉमिस दिखाए.

साल 2014 में, मुझे असाइन फ़ंक्शन के बारे में जानकर काफ़ी खुशी हुई थी. अब उन्हें ब्राउज़र में इस्तेमाल किया जा सकता है. वाह!