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

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

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

एक साथ काम नहीं करने वाले फ़ंक्शन इस तरह काम करते हैं:

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

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

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

ब्राउज़र सहायता

  • Chrome: 55. अभी तक किसी भी व्यक्ति ने चेक इन नहीं किया है
  • एज: 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 लूप को 'टू-ऑफ़-लूप' से बदलें, ताकि यह और साफ़ हो जाए.

अन्य एक साथ काम नहीं करने वाला फ़ंक्शन सिंटैक्स

मैंने आपको पहले ही 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 बिट को स्टैंडर्ड, बोरिंग, और लूप में पढ़ने की सुविधा से बदल दिया गया है.

ब्राउज़र से जुड़ी सहायता का समाधान: जनरेटर

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

बाबेल आपके लिए यह कर देगा, यहां बेबल REPL का एक उदाहरण दिया गया है

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

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

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

...आप पॉलीफ़िल को भी शामिल करेंगे और लिखें:

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

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

समाधान: रीजनरेटर

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

आउटपुट उतना अच्छा नहीं है, इसलिए कोड-ब्लोट.

सभी चीज़ें एक साथ सिंक न हों!

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

मैं एक साथ काम नहीं करने वाले फ़ंक्शन को लेकर पहले ही बहुत उत्साहित हो गया था 2014 में और वाकई में ब्राउज़र पर आकर, उन्हें अपनी साइट पर पहुंचते हुए देखकर अच्छा लगता है. ओह!