آئیے مثالوں کا استعمال کرتے ہوئے جاوا اسکرپٹ میں Async/Await کو دیکھتے ہیں۔

مضمون کا مصنف JavaScript میں Async/Await کی مثالوں کا جائزہ لیتا ہے۔ مجموعی طور پر، Async/Await غیر مطابقت پذیر کوڈ لکھنے کا ایک آسان طریقہ ہے۔ اس خصوصیت کے ظاہر ہونے سے پہلے، اس طرح کا کوڈ کال بیکس اور وعدوں کا استعمال کرتے ہوئے لکھا جاتا تھا۔ اصل مضمون کا مصنف مختلف مثالوں کا تجزیہ کرکے Async/Await کے فوائد کو ظاہر کرتا ہے۔

ہم آپ کو یاد دلاتے ہیں: "Habr" کے تمام قارئین کے لیے - "Habr" پروموشنل کوڈ کا استعمال کرتے ہوئے کسی بھی Skillbox کورس میں داخلہ لینے پر 10 rubles کی رعایت۔

Skillbox تجویز کرتا ہے: تعلیمی آن لائن کورس "جاوا ڈویلپر".

کال بیک

کال بیک ایک ایسا فنکشن ہے جس کی کال میں غیر معینہ مدت تک تاخیر ہوتی ہے۔ پہلے، کال بیکس کوڈ کے ان علاقوں میں استعمال کیا جاتا تھا جہاں نتیجہ فوری طور پر حاصل نہیں کیا جا سکتا تھا۔

Node.js میں کسی فائل کو متضاد طور پر پڑھنے کی ایک مثال یہ ہے:

fs.readFile(__filename, 'utf-8', (err, data) => {
  if (err) {
    throw err;
  }
  console.log(data);
});

مسائل اس وقت پیدا ہوتے ہیں جب آپ کو ایک ساتھ کئی غیر مطابقت پذیر آپریشن کرنے کی ضرورت ہوتی ہے۔ آئیے اس منظر نامے کا تصور کریں: عرفات صارف کے ڈیٹا بیس سے ایک درخواست کی گئی ہے، آپ کو اس کی profile_img_url فیلڈ کو پڑھنے اور someserver.com سرور سے ایک تصویر ڈاؤن لوڈ کرنے کی ضرورت ہے۔
ڈاؤن لوڈ کرنے کے بعد، ہم تصویر کو دوسرے فارمیٹ میں تبدیل کرتے ہیں، مثال کے طور پر PNG سے JPEG میں۔ اگر تبدیلی کامیاب رہی تو صارف کے ای میل پر ایک خط بھیجا جاتا ہے۔ اگلا، واقعہ کے بارے میں معلومات transformations.log فائل میں درج کی جاتی ہے، جو تاریخ کی نشاندہی کرتی ہے۔

کوڈ کے آخری حصے میں کال بیکس کے اوورلیپ اور }) کی بڑی تعداد پر توجہ دینے کے قابل ہے۔ اسے کال بیک ہیل یا پیرامڈ آف ڈوم کہتے ہیں۔

اس طریقہ کار کے نقصانات واضح ہیں:

  • اس کوڈ کو پڑھنا مشکل ہے۔
  • غلطیوں کو سنبھالنا بھی مشکل ہے، جس کی وجہ سے اکثر کوڈ کا معیار خراب ہوتا ہے۔

اس مسئلے کو حل کرنے کے لیے جاوا اسکرپٹ میں وعدے شامل کیے گئے۔ وہ آپ کو کال بیکس کے گہرے گھونسلے کو لفظ کے ساتھ تبدیل کرنے کی اجازت دیتے ہیں۔

وعدوں کا مثبت پہلو یہ ہے کہ وہ کوڈ کو بائیں سے دائیں کی بجائے اوپر سے نیچے تک بہت بہتر پڑھنے کے قابل بناتے ہیں۔ تاہم، وعدوں کے بھی اپنے مسائل ہیں:

  • آپ کو بہت کچھ شامل کرنے کی ضرورت ہے۔
  • کوشش/کیچ کے بجائے، تمام خرابیوں کو سنبھالنے کے لیے .catch استعمال کیا جاتا ہے۔
  • ایک لوپ کے اندر متعدد وعدوں کے ساتھ کام کرنا ہمیشہ آسان نہیں ہوتا؛ بعض صورتوں میں، وہ کوڈ کو پیچیدہ بنا دیتے ہیں۔

یہاں ایک مسئلہ ہے جو آخری نقطہ کے معنی کو ظاہر کرے گا۔

فرض کریں کہ ہمارے پاس ایک لوپ ہے جو بے ترتیب وقفوں (0–n سیکنڈ) پر 10 سے 0 تک نمبروں کی ترتیب پرنٹ کرتا ہے۔ وعدوں کا استعمال کرتے ہوئے، آپ کو اس لوپ کو تبدیل کرنے کی ضرورت ہے تاکہ نمبر 0 سے 10 تک ترتیب سے پرنٹ ہوں۔ لہذا، اگر صفر کو پرنٹ کرنے میں 6 سیکنڈ اور ایک کو پرنٹ کرنے میں 2 سیکنڈ لگتے ہیں، تو پہلے صفر کو پرنٹ کیا جانا چاہیے، اور پھر ایک کو پرنٹ کرنے کے لیے الٹی گنتی شروع ہو جائے گی۔

اور ظاہر ہے، ہم اس مسئلے کو حل کرنے کے لیے Async/Await یا .sort کا استعمال نہیں کرتے ہیں۔ ایک مثال حل آخر میں ہے۔

Async افعال

ES2017 (ES8) میں async فنکشنز کے اضافے نے وعدوں کے ساتھ کام کرنے کے کام کو آسان بنا دیا۔ میں نوٹ کرتا ہوں کہ async افعال وعدوں کے "اوپر" کام کرتے ہیں۔ یہ افعال معیار کے لحاظ سے مختلف تصورات کی نمائندگی نہیں کرتے ہیں۔ Async افعال کا مقصد کوڈ کے متبادل کے طور پر ہے جو وعدوں کو استعمال کرتا ہے۔

Async/Await مطابقت پذیر انداز میں غیر مطابقت پذیر کوڈ کے ساتھ کام کو منظم کرنا ممکن بناتا ہے۔

اس طرح، وعدوں کو جاننے سے Async/Await کے اصولوں کو سمجھنا آسان ہو جاتا ہے۔

نحو

عام طور پر یہ دو کلیدی الفاظ پر مشتمل ہوتا ہے: async اور await۔ پہلا لفظ فنکشن کو غیر مطابقت پذیر میں بدل دیتا ہے۔ اس طرح کے افعال انتظار کے استعمال کی اجازت دیتے ہیں۔ کسی بھی دوسری صورت میں، اس فنکشن کو استعمال کرنے سے ایک خرابی پیدا ہوگی۔

// With function declaration
 
async function myFn() {
  // await ...
}
 
// With arrow function
 
const myFn = async () => {
  // await ...
}
 
function myFn() {
  // await fn(); (Syntax Error since no async)
}
 

Async فنکشن ڈیکلریشن کے بالکل شروع میں داخل کیا جاتا ہے، اور تیر کے فنکشن کی صورت میں، "=" نشان اور قوسین کے درمیان۔

ان افعال کو کسی شے میں بطور طریقوں یا کلاس ڈیکلریشن میں استعمال کیا جا سکتا ہے۔

// As an object's method
 
const obj = {
  async getName() {
    return fetch('https://www.example.com');
  }
}
 
// In a class
 
class Obj {
  async getResource() {
    return fetch('https://www.example.com');
  }
}

NB! یہ یاد رکھنے کے قابل ہے کہ کلاس کنسٹرکٹرز اور گیٹرز/سیٹرز غیر مطابقت پذیر نہیں ہوسکتے ہیں۔

سیمنٹکس اور عمل درآمد کے قواعد

Async فنکشنز بنیادی طور پر معیاری JS فنکشنز سے ملتے جلتے ہیں، لیکن اس میں مستثنیات ہیں۔

اس طرح، async افعال ہمیشہ وعدے واپس کرتے ہیں:

async function fn() {
  return 'hello';
}
fn().then(console.log)
// hello

خاص طور پر، fn سٹرنگ ہیلو کو لوٹاتا ہے۔ ٹھیک ہے، چونکہ یہ ایک متضاد فنکشن ہے، اس لیے اسٹرنگ ویلیو کو کنسٹرکٹر کا استعمال کرتے ہوئے وعدے میں لپیٹا جاتا ہے۔

یہاں Async کے بغیر ایک متبادل ڈیزائن ہے:

function fn() {
  return Promise.resolve('hello');
}
 
fn().then(console.log);
// hello

اس صورت میں، وعدہ "دستی طور پر" واپس آ جاتا ہے۔ ایک غیر مطابقت پذیر فنکشن ہمیشہ ایک نئے وعدے میں لپیٹا جاتا ہے۔

اگر واپسی کی قیمت ایک قدیم ہے، تو async فنکشن اسے وعدے میں لپیٹ کر قدر واپس کرتا ہے۔ اگر واپسی کی قیمت ایک وعدہ آبجیکٹ ہے، تو اس کی ریزولیوشن ایک نئے وعدے میں واپس کی جاتی ہے۔

const p = Promise.resolve('hello')
p instanceof Promise;
// true
 
Promise.resolve(p) === p;
// true
 

لیکن کیا ہوتا ہے اگر کسی غیر مطابقت پذیر فنکشن کے اندر کوئی خرابی ہو؟

async function foo() {
  throw Error('bar');
}
 
foo().catch(console.log);

اگر اس پر کارروائی نہیں کی جاتی ہے تو، foo() ایک وعدہ مسترد کر کے واپس کر دے گا۔ اس صورت حال میں، Promise.resolve کے بجائے ایک غلطی پر مشتمل Promise.reject واپس کر دیا جائے گا۔

Async فنکشنز ہمیشہ وعدے کی پیداوار کرتے ہیں، قطع نظر اس کے کہ کیا واپس کیا گیا ہے۔

غیر متزلزل افعال ہر انتظار پر توقف کرتے ہیں۔

انتظار تاثرات کو متاثر کرتا ہے۔ لہذا، اگر اظہار ایک وعدہ ہے تو، async فنکشن اس وقت تک معطل رہے گا جب تک کہ وعدہ پورا نہ ہو۔ اگر اظہار وعدہ نہیں ہے، تو اسے Promise.resolve کے ذریعے وعدے میں تبدیل کیا جاتا ہے اور پھر مکمل کیا جاتا ہے۔

// utility function to cause delay
// and get random value
 
const delayAndGetRandom = (ms) => {
  return new Promise(resolve => setTimeout(
    () => {
      const val = Math.trunc(Math.random() * 100);
      resolve(val);
    }, ms
  ));
};
 
async function fn() {
  const a = await 9;
  const b = await delayAndGetRandom(1000);
  const c = await 5;
  await delayAndGetRandom(1000);
 
  return a + b * c;
}
 
// Execute fn
fn().then(console.log);

اور یہاں ایک تفصیل ہے کہ fn فنکشن کیسے کام کرتا ہے۔

  • اسے کال کرنے کے بعد، پہلی لائن const a = await 9 سے بدل جاتی ہے۔ in const a = انتظار کریں Promise.resolve(9)؛۔
  • Await استعمال کرنے کے بعد، فنکشن کو اس وقت تک معطل کر دیا جاتا ہے جب تک کہ اسے اس کی قیمت نہ مل جائے (موجودہ صورتحال میں یہ 9 ہے)۔
  • delayAndGetRandom(1000) fn فنکشن کے عمل کو اس وقت تک روکتا ہے جب تک کہ یہ خود مکمل نہ ہوجائے (1 سیکنڈ کے بعد)۔ یہ مؤثر طریقے سے 1 سیکنڈ کے لیے fn فنکشن کو روکتا ہے۔
  • delayAndGetRandom(1000) بذریعہ حل ایک بے ترتیب قدر لوٹاتا ہے، جسے پھر متغیر b کو تفویض کیا جاتا ہے۔
  • ٹھیک ہے، متغیر c کا معاملہ متغیر a کے معاملے سے ملتا جلتا ہے۔ اس کے بعد، سب کچھ ایک سیکنڈ کے لیے رک جاتا ہے، لیکن اب delayAndGetRandom(1000) کچھ نہیں دیتا کیونکہ اس کی ضرورت نہیں ہے۔
  • نتیجے کے طور پر، اقدار کا حساب فارمولہ a + b * c استعمال کرتے ہوئے کیا جاتا ہے۔ نتیجہ Promise.resolve کا استعمال کرتے ہوئے وعدے میں لپیٹا جاتا ہے اور فنکشن کے ذریعے واپس آتا ہے۔

یہ وقفے ES6 میں جنریٹرز کی یاد تازہ کر سکتے ہیں، لیکن اس میں کچھ ہے۔ آپ کی وجوہات.

مسئلہ حل کرنا

ٹھیک ہے، اب اوپر بیان کردہ مسئلے کا حل دیکھتے ہیں۔

FinishMyTask فنکشن Await کو آپریشنز کے نتائج کا انتظار کرنے کے لیے استعمال کرتا ہے جیسے queryDatabase، sendEmail، logTaskInFile، اور دیگر۔ اگر آپ اس حل کا موازنہ اس حل سے کریں جہاں وعدے کیے گئے تھے، تو مماثلتیں واضح ہو جائیں گی۔ تاہم، Async/Await ورژن تمام نحوی پیچیدگیوں کو بہت آسان بنا دیتا ہے۔ اس صورت میں، کال بیکس اور زنجیروں کی کوئی بڑی تعداد نہیں ہے جیسے .then/.catch۔

یہاں نمبرز کی آؤٹ پٹ کے ساتھ ایک حل ہے، دو آپشنز ہیں۔

const wait = (i, ms) => new Promise(resolve => setTimeout(() => resolve(i), ms));
 
// Implementation One (Using for-loop)
const printNumbers = () => new Promise((resolve) => {
  let pr = Promise.resolve(0);
  for (let i = 1; i <= 10; i += 1) {
    pr = pr.then((val) => {
      console.log(val);
      return wait(i, Math.random() * 1000);
    });
  }
  resolve(pr);
});
 
// Implementation Two (Using Recursion)
 
const printNumbersRecursive = () => {
  return Promise.resolve(0).then(function processNextPromise(i) {
 
    if (i === 10) {
      return undefined;
    }
 
    return wait(i, Math.random() * 1000).then((val) => {
      console.log(val);
      return processNextPromise(i + 1);
    });
  });
};

اور یہاں async افعال کا استعمال کرتے ہوئے ایک حل ہے۔

async function printNumbersUsingAsync() {
  for (let i = 0; i < 10; i++) {
    await wait(i, Math.random() * 1000);
    console.log(i);
  }
}

سنبھلنے میں خرابی۔

غیر ہینڈل شدہ غلطیاں مسترد شدہ وعدے میں لپٹی ہوئی ہیں۔ تاہم، async فنکشنز غلطیوں کو ہم آہنگی سے ہینڈل کرنے کے لیے try/catch کا استعمال کر سکتے ہیں۔

async function canRejectOrReturn() {
  // wait one second
  await new Promise(res => setTimeout(res, 1000));
 
// Reject with ~50% probability
  if (Math.random() > 0.5) {
    throw new Error('Sorry, number too big.')
  }
 
return 'perfect number';
}

canRejectOrReturn() ایک غیر مطابقت پذیر فنکشن ہے جو یا تو کامیاب ہوتا ہے ("پرفیکٹ نمبر") یا کسی غلطی کے ساتھ ناکام ہوجاتا ہے ("معذرت، نمبر بہت بڑا")۔

async function foo() {
  try {
    await canRejectOrReturn();
  } catch (e) {
    return 'error caught';
  }
}

چونکہ اوپر دی گئی مثال canRejectOrReturn پر عملدرآمد کی توقع رکھتی ہے، اس لیے اس کی اپنی ناکامی کا نتیجہ کیچ بلاک پر عمل درآمد ہو گا۔ نتیجے کے طور پر، فنکشن foo یا تو undefined کے ساتھ ختم ہو جائے گا (جب ٹرائی بلاک میں کچھ بھی واپس نہیں کیا جاتا ہے) یا ایک غلطی پکڑی گئی ہے۔ نتیجے کے طور پر، یہ فنکشن ناکام نہیں ہوگا کیونکہ try/catch فنکشن foo کو ہینڈل کرے گا۔

یہاں ایک اور مثال ہے:

async function foo() {
  try {
    return canRejectOrReturn();
  } catch (e) {
    return 'error caught';
  }
}

اس حقیقت پر توجہ دینے کے قابل ہے کہ مثال کے طور پر، canRejectOrReturn foo سے واپس آ گیا ہے۔ Foo اس معاملے میں یا تو کامل نمبر کے ساتھ ختم ہو جاتا ہے یا پھر ایک خرابی لوٹاتا ہے ("معذرت، نمبر بہت بڑا")۔ کیچ بلاک پر کبھی عمل نہیں کیا جائے گا۔

مسئلہ یہ ہے کہ foo canRejectOrReturn سے منظور شدہ وعدہ واپس کرتا ہے۔ تو foo کا حل canRejectOrReturn کا حل بن جاتا ہے۔ اس صورت میں، کوڈ صرف دو لائنوں پر مشتمل ہوگا:

try {
    const promise = canRejectOrReturn();
    return promise;
}

اگر آپ انتظار اور واپسی کو ایک ساتھ استعمال کرتے ہیں تو کیا ہوتا ہے:

async function foo() {
  try {
    return await canRejectOrReturn();
  } catch (e) {
    return 'error caught';
  }
}

مندرجہ بالا کوڈ میں، foo ایک مکمل نمبر اور پکڑی گئی غلطی دونوں کے ساتھ کامیابی سے نکل جائے گا۔ یہاں کوئی انکار نہیں ہوگا۔ لیکن foo canRejectOrReturn کے ساتھ واپس آئے گا، undefined کے ساتھ نہیں۔ آئیے ریٹرن await canRejectOrReturn() لائن کو ہٹا کر اس بات کو یقینی بنائیں:

try {
    const value = await canRejectOrReturn();
    return value;
}
// …

عام غلطیاں اور نقصانات

کچھ صورتوں میں، Async/Await کا استعمال غلطیاں پیدا کر سکتا ہے۔

بھول گئے انتظار

ایسا اکثر ہوتا ہے - await کلیدی لفظ وعدے سے پہلے بھول جاتا ہے:

async function foo() {
  try {
    canRejectOrReturn();
  } catch (e) {
    return 'caught';
  }
}

جیسا کہ آپ دیکھ سکتے ہیں، کوڈ میں کوئی انتظار یا واپسی نہیں ہے۔ لہذا foo ہمیشہ 1 سیکنڈ کی تاخیر کے بغیر غیر وضاحتی کے ساتھ نکل جاتا ہے۔ لیکن وعدہ پورا ہو گا۔ اگر یہ ایک غلطی یا مسترد کرتا ہے، تو UnhandledPromiseRejectionWarning کہا جائے گا۔

کال بیکس میں Async فنکشنز

Async فنکشنز اکثر .map یا .filter میں کال بیکس کے طور پر استعمال ہوتے ہیں۔ ایک مثال fetchPublicReposCount(username) فنکشن ہے، جو GitHub پر کھلے ریپوزٹریوں کی تعداد لوٹاتا ہے۔ ہم کہتے ہیں کہ تین ایسے صارفین ہیں جن کے میٹرکس کی ہمیں ضرورت ہے۔ اس کام کا کوڈ یہ ہے:

const url = 'https://api.github.com/users';
 
// Utility fn to fetch repo counts
const fetchPublicReposCount = async (username) => {
  const response = await fetch(`${url}/${username}`);
  const json = await response.json();
  return json['public_repos'];
}

ہمیں ArfatSalman، octocat، norvig اکاؤنٹس کی ضرورت ہے۔ اس معاملے میں ہم کرتے ہیں:

const users = [
  'ArfatSalman',
  'octocat',
  'norvig'
];
 
const counts = users.map(async username => {
  const count = await fetchPublicReposCount(username);
  return count;
});

یہ .map کال بیک میں انتظار پر توجہ دینے کے قابل ہے۔ یہاں شمار وعدوں کی ایک صف ہے، اور .map ہر مخصوص صارف کے لیے ایک گمنام کال بیک ہے۔

انتظار کا حد سے زیادہ مسلسل استعمال

آئیے اس کوڈ کو بطور مثال لیتے ہیں:

async function fetchAllCounts(users) {
  const counts = [];
  for (let i = 0; i < users.length; i++) {
    const username = users[i];
    const count = await fetchPublicReposCount(username);
    counts.push(count);
  }
  return counts;
}

یہاں ریپو نمبر کو کاؤنٹ ویری ایبل میں رکھا جاتا ہے، پھر اس نمبر کو شمار کی صف میں شامل کیا جاتا ہے۔ کوڈ کے ساتھ مسئلہ یہ ہے کہ جب تک سرور سے پہلے صارف کا ڈیٹا نہیں آتا، اس کے بعد آنے والے تمام صارفین اسٹینڈ بائی موڈ میں ہوں گے۔ اس طرح، ایک وقت میں صرف ایک صارف پر کارروائی ہوتی ہے۔

اگر، مثال کے طور پر، ایک صارف کو پروسیس کرنے میں تقریباً 300 ms کا وقت لگتا ہے، تو تمام صارفین کے لیے یہ پہلے سے ہی ایک سیکنڈ ہے؛ خطی طور پر گزارا جانے والا وقت صارفین کی تعداد پر منحصر ہے۔ لیکن چونکہ ریپو کی تعداد حاصل کرنا ایک دوسرے پر منحصر نہیں ہے، اس لیے عمل کو متوازی بنایا جا سکتا ہے۔ اس کے لیے .map اور Promise.all کے ساتھ کام کرنے کی ضرورت ہے:

async function fetchAllCounts(users) {
  const promises = users.map(async username => {
    const count = await fetchPublicReposCount(username);
    return count;
  });
  return Promise.all(promises);
}

Promise.all ان پٹ کے طور پر وعدوں کی ایک صف حاصل کرتا ہے اور وعدہ واپس کرتا ہے۔ مؤخر الذکر صف میں موجود تمام وعدوں کے مکمل ہونے کے بعد یا پہلے مسترد ہونے پر مکمل ہوتا ہے۔ یہ ہو سکتا ہے کہ وہ سب ایک ہی وقت میں شروع نہ ہوں - بیک وقت آغاز کو یقینی بنانے کے لیے، آپ p-map استعمال کر سکتے ہیں۔

حاصل يہ ہوا

Async افعال ترقی کے لیے تیزی سے اہم ہوتے جا رہے ہیں۔ ٹھیک ہے، async افعال کے انکولی استعمال کے لیے، آپ کو استعمال کرنا چاہیے۔ Async Iterators. جاوا اسکرپٹ ڈویلپر کو اس میں اچھی طرح مہارت حاصل ہونی چاہئے۔

Skillbox تجویز کرتا ہے:

ماخذ: www.habr.com

نیا تبصرہ شامل کریں