ምሳሌዎችን በመጠቀም Async/Await in JavaScriptን እንይ

የጽሁፉ ደራሲ በጃቫስክሪፕት ውስጥ Async/Await ምሳሌዎችን ይመረምራል። በአጠቃላይ፣ Async/Await ያልተመሳሰለ ኮድ ለመፃፍ ምቹ መንገድ ነው። ይህ ባህሪ ከመታየቱ በፊት እንዲህ ዓይነቱ ኮድ የተፃፈው መልሶ ጥሪዎችን እና ተስፋዎችን በመጠቀም ነው። የዋናው መጣጥፍ ደራሲ የተለያዩ ምሳሌዎችን በመተንተን የአሲንክ/አዋይት ጥቅሞችን ያሳያል።

እኛ እናስታውስዎታለን- ለሁሉም የ "ሀብር" አንባቢዎች - የ "Habr" የማስተዋወቂያ ኮድን በመጠቀም በማንኛውም የ Skillbox ኮርስ ውስጥ ሲመዘገቡ የ 10 ሩብልስ ቅናሽ.

Skillbox ይመክራል፡ ትምህርታዊ የመስመር ላይ ኮርስ "የጃቫ ገንቢ".

ተዘዋዋሪ

መልሶ መደወል ጥሪው ላልተወሰነ ጊዜ የዘገየ ተግባር ነው። ከዚህ ቀደም መልሶ መደወል ውጤቱን ወዲያውኑ ማግኘት በማይቻልባቸው የኮድ ቦታዎች ላይ ጥቅም ላይ ውሎ ነበር።

በ Node.js ውስጥ አንድን ፋይል ያልተመሳሰል የማንበብ ምሳሌ ይኸውና፡

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

ብዙ ያልተመሳሰሉ ስራዎችን በአንድ ጊዜ ማከናወን ሲያስፈልግ ችግሮች ይከሰታሉ። እስቲ ይህን ሁኔታ በዓይነ ሕሊናህ እናስብ፡ ለአርፋት ተጠቃሚ ዳታቤዝ ጥያቄ ቀርቧል፡ የፕሮፋይል_img_url መስኩን ማንበብ እና ከsomeserver.com አገልጋይ ምስል ማውረድ አለብህ።
ካወረድን በኋላ ምስሉን ወደ ሌላ ቅርጸት እንለውጣለን, ለምሳሌ ከ PNG ወደ JPEG. ልወጣው የተሳካ ከሆነ ለተጠቃሚው ኢሜይል ደብዳቤ ይላካል። በመቀጠል, ስለ ክስተቱ መረጃ ቀኑን የሚያመለክት ወደ transformations.log ፋይል ውስጥ ይገባል.

በኮዱ የመጨረሻ ክፍል ላይ የመልሶ መደወያ መደራረብ እና ብዙ ቁጥር}) ትኩረት መስጠት ተገቢ ነው። የመልሶ ጥሪ ሲኦል ወይም ፒራሚድ ኦፍ ዶም ይባላል።

የዚህ ዘዴ ጉዳቶች ግልጽ ናቸው-

  • ይህ ኮድ ለማንበብ አስቸጋሪ ነው።
  • እንዲሁም ስህተቶችን ማስተናገድ አስቸጋሪ ነው, ይህም ብዙውን ጊዜ ወደ ደካማ የኮድ ጥራት ይመራል.

ይህንን ችግር ለመፍታት፣ ተስፋዎች ወደ ጃቫስክሪፕት ተጨምረዋል። የመልሶ መደወያዎችን ጥልቅ ጎጆ .ከዛ በሚለው ቃል እንድትተኩ ያስችሉሃል።

የተስፋዎቹ አወንታዊ ገጽታ ኮዱን ከግራ ወደ ቀኝ ሳይሆን ከላይ ወደ ታች እንዲነበብ ማድረጋቸው ነው። ሆኖም፣ ተስፋዎች ችግሮቻቸውም አሉባቸው፡-

  • ከዚያ ብዙ ማከል ያስፈልግዎታል።
  • ከመሞከር / ከመያዝ ይልቅ, .catch ሁሉንም ስህተቶች ለማስተናገድ ጥቅም ላይ ይውላል.
  • በአንድ ዙር ውስጥ ከበርካታ ተስፋዎች ጋር መስራት ሁልጊዜ ምቹ አይደለም፤ በአንዳንድ ሁኔታዎች ኮዱን ያወሳስባሉ።

የመጨረሻውን ነጥብ ትርጉም የሚያሳይ ችግር እዚህ አለ.

በዘፈቀደ ክፍተቶች (0–n ሰከንድ) ላይ ተከታታይ ቁጥሮችን ከ10 እስከ 0 የሚታተም ለ loop አለን እንበል። ቃል ኪዳኖችን በመጠቀም ቁጥሮቹ በቅደም ተከተል ከ 0 ወደ 10 እንዲታተሙ ይህንን ዑደት መለወጥ ያስፈልግዎታል ። ስለዚህ ዜሮ ለማተም 6 ሰከንድ እና አንድን ለማተም 2 ሴኮንድ የሚወስድ ከሆነ ዜሮ መጀመሪያ መታተም አለበት እና ከዚያ የህትመት ቆጠራው ይጀምራል።

እና በእርግጥ፣ ይህንን ችግር ለመፍታት Async/Await ወይም .sort አንጠቀምም። አንድ ምሳሌ መፍትሄ መጨረሻ ላይ ነው.

የማመሳሰል ተግባራት

በ ES2017 (ES8) ውስጥ የማመሳሰል ተግባራትን መጨመር ከተስፋዎች ጋር የመስራትን ስራ ቀላል አድርጎታል። የማመሳሰል ተግባራት በተስፋዎች ላይ "በላይ" እንደሚሰሩ አስተውያለሁ. እነዚህ ተግባራት በጥራት የተለያዩ ጽንሰ-ሐሳቦችን አይወክሉም. የማመሳሰል ተግባራት ተስፋዎችን ከሚጠቀም ኮድ እንደ አማራጭ የታሰቡ ናቸው።

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 function fn() {
  return 'hello';
}
fn().then(console.log)
// hello

በተለይ fn ሕብረቁምፊውን ሰላም ይመልሳል። ደህና፣ ይህ ያልተመሳሰለ ተግባር ስለሆነ፣ የሕብረቁምፊው ዋጋ ገንቢን በመጠቀም በቃል ተጠቅልሏል።

ያለ Async አማራጭ ንድፍ ይኸውና፡

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

በዚህ ሁኔታ, ተስፋው "በእጅ" ይመለሳል. ያልተመሳሰለ ተግባር ሁል ጊዜ በአዲስ ቃል ውስጥ ይጠቀለላል።

የመመለሻ እሴቱ ጥንታዊ ከሆነ፣ የአስመር ተግባር እሴቱን በቃል በመጠቅለል ይመልሳል። የመመለሻ እሴቱ የተስፋ ቃል ከሆነ ፣የውሳኔው አዲስ ቃል ውስጥ ይመለሳል።

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.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 = መጠበቅ 9 ይቀየራል; በ const a = ይጠብቁ ቃል ኪዳን. መፍትሄ (9);.
  • አዋይትን ከተጠቀሙ በኋላ የተግባር አፈፃፀም አንድ እሴቱን እስኪያገኝ ድረስ ይታገዳል (አሁን ባለው ሁኔታ 9 ነው)።
  • delayAndGetRandom(1000) የfn ተግባሩን እራሱን እስኪያጠናቅቅ ድረስ ለአፍታ ያቆማል (ከ1 ሰከንድ በኋላ)። ይህ ለ 1 ሰከንድ የ fn ተግባሩን በትክክል ያቆማል።
  • delayAndGetRandom(1000) በመፍታት በዘፈቀደ እሴት ይመልሳል፣ ይህም ለተለዋዋጭ ይመደባል ለ.
  • ደህና፣ ከተለዋዋጭ ሐ ጋር ያለው ጉዳይ ከተለዋዋጭ ሀ ጋር ተመሳሳይ ነው። ከዚያ በኋላ፣ ሁሉም ነገር ለአንድ ሰከንድ ይቆማል፣ አሁን ግን delayAndGetRandom(1000) ምንም አይመልስም ምክንያቱም አያስፈልግም።
  • በውጤቱም, እሴቶቹ በቀመር a + b * c በመጠቀም ይሰላሉ. ውጤቱ በ Promise.resolve ተጠቅልሎ በተግባሩ ተመልሷል።

እነዚህ ባለበት ማቆም በES6 ውስጥ ያሉትን ጄነሬተሮች የሚያስታውሱ ሊሆኑ ይችላሉ፣ ነገር ግን ለእሱ የሆነ ነገር አለ። ምክንያቶችህ.

ችግሩን መፍታት

ደህና ፣ አሁን ከላይ ለተጠቀሰው ችግር መፍትሄውን እንመልከት ።

የማጠናቀቂያው MyTask ተግባር እንደ መጠይቅ ዳታቤዝ፣ ኢሜይል መላኪያ፣ logTaskInFile እና ሌሎች ያሉ ስራዎችን ውጤት ለመጠበቅ ዋይትን ይጠቀማል። ይህንን መፍትሄ ተስፋዎች ጥቅም ላይ ከዋሉበት ጋር ካነጻጸሩት, ተመሳሳይነት ግልጽ ይሆናል. ሆኖም፣ የAsync/Await እትም ሁሉንም የአገባብ ውስብስብ ነገሮችን በእጅጉ ያቃልላል። በዚህ አጋጣሚ፣ እንደ .ከዚያ/ .መያዣ ያሉ ብዙ ቁጥር ያላቸው መልሶ መደወያዎች እና ሰንሰለቶች የሉም።

ከቁጥሮች ውፅዓት ጋር አንድ መፍትሄ እዚህ አለ ፣ ሁለት አማራጮች አሉ።

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 function printNumbersUsingAsync() {
  for (let i = 0; i < 10; i++) {
    await wait(i, Math.random() * 1000);
    console.log(i);
  }
}

በማስኬድ ላይ ስህተት

ያልተያዙ ስህተቶች ውድቅ በሆነ ቃል ውስጥ ተጠቅልለዋል። ሆኖም ግን፣ የማመሳሰል ተግባራት ስህተቶችን በተመሳሳይ ጊዜ ለማስተናገድ መሞከር/መያዝን መጠቀም ይችላሉ።

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

ከላይ ያለው ምሳሌ RejectOrReturn ወደ መፈጸም ስለሚጠብቅ የራሱ ውድቀት የያዙትን እገዳ ያስከትላል። በውጤቱም፣ ተግባሩ ባልተገለጸ (በሙከራ እገዳው ውስጥ ምንም ነገር ካልተመለሰ) ወይም ከተያዘ ስህተት ጋር ያበቃል። በውጤቱም, ይህ ተግባር አይሳካም ምክንያቱም ሙከራው / መያዣው ተግባሩን በራሱ ይቆጣጠራል.

ሌላ ምሳሌ ይኸውና፡-

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

በምሳሌው ውስጥ canRejectOrReturn ከ foo መመለሱን ትኩረት መስጠቱ ተገቢ ነው። Foo በዚህ አጋጣሚ ወይ በፍፁም ቁጥር ያበቃል ወይም ስህተት ይመልሳል ("ይቅርታ፣ ቁጥር በጣም ትልቅ")። የመያዣው እገዳ ፈጽሞ አይፈጸምም.

ችግሩ foo ከ canRejectOrReturn የተላለፈውን ቃል መመለሱ ነው። ስለዚህ ለ fo ያለው መፍትሔ ለ canRejectOrReturn መፍትሔ ይሆናል። በዚህ ሁኔታ, ኮዱ ሁለት መስመሮችን ብቻ ያካትታል.

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

ይጠብቁን እና አብረው ከተመለሱ ምን እንደሚፈጠር እነሆ፡-

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

ከላይ ባለው ኮድ፣ በሁለቱም ፍጹም ቁጥር እና ስህተት ከተያዘ foo በተሳካ ሁኔታ ይወጣል። እዚህ ምንም እምቢታ አይኖርም. ነገር ግን foo በ canRejectOrReturn ይመለሳል፣ ባልተገለጸው ሳይሆን። መመለሻ መጠባበቅ canRejectOrReturn() መስመርን በማስወገድ ይህንን እናረጋግጥ፡-

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

የተለመዱ ስህተቶች እና ጉድለቶች

በአንዳንድ አጋጣሚዎች Async/Await መጠቀም ወደ ስህተቶች ሊያመራ ይችላል።

የተረሳ መጠበቅ

ይህ በጣም ብዙ ጊዜ ይከሰታል - የመጠባበቅ ቁልፍ ቃል ከተስፋው በፊት ይረሳል-

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

እንደሚመለከቱት, በኮዱ ውስጥ ምንም መጠበቅ ወይም መመለስ የለም. ስለዚህ foo ሁል ጊዜ ያለ 1 ሰከንድ ሳይገለፅ ይወጣል። የተስፋው ቃል ግን ይፈጸማል። ስህተት ወይም ውድቅ ካደረገ፣ UnhandledPromiseRejectionWarning ይባላል።

ተመሳስለው ተግባራት በመልሶ ጥሪዎች ውስጥ

የማመሳሰል ተግባራት ብዙውን ጊዜ በካርታ ወይም በማጣሪያ ውስጥ እንደ መልሶ ጥሪዎች ያገለግላሉ። ለምሳሌ የfetchPublicReposCount(የተጠቃሚ ስም) ተግባር ነው፣ እሱም በ 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 accounts እንፈልጋለን። በዚህ ሁኔታ እኛ እናደርጋለን-

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

በካርታው መልሶ መደወል ላይ በትኩረት መከታተል ተገቢ ነው። እዚህ ይቆጠራሉ የተስፋዎች ድርድር ነው፣ እና .ካርታ ለእያንዳንዱ የተገለጸ ተጠቃሚ የማይታወቅ መልሶ መደወል ነው።

ከመጠን በላይ ወጥነት ያለው ጥበቃ አጠቃቀም

ይህንን ኮድ እንደ ምሳሌ እንውሰድ፡-

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 ሚሴ ያህል የሚፈጅ ከሆነ፣ ለሁሉም ተጠቃሚዎች ቀድሞውንም ሰከንድ ነው፣ በመስመር ላይ የሚፈጀው ጊዜ በተጠቃሚዎች ብዛት ላይ የተመሰረተ ነው። ነገር ግን የሪፖዎችን ቁጥር ማግኘት እርስ በእርሳቸው ላይ የተመካ ስላልሆነ ሂደቶቹ ሊመሳሰሉ ይችላሉ. ይህ በካርታ እና በPromise.all መስራትን ይጠይቃል፡-

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

ቃል ኪዳን.ሁሉም እንደ ግብአት የተለያዩ የተስፋ ቃሎችን ይቀበላል እና ቃል ኪዳንን ይመልሳል። የኋለኛው ፣ በድርድር ውስጥ ያሉት ሁሉም ተስፋዎች ከተጠናቀቁ በኋላ ወይም በመጀመሪያ ውድቅት ፣ ይጠናቀቃሉ። ሁሉም በአንድ ጊዜ የማይጀምሩ ሊሆኑ ይችላሉ - በአንድ ጊዜ መጀመርን ለማረጋገጥ ፒ-ካርታ መጠቀም ይችላሉ።

መደምደሚያ

የአሲንክ ተግባራት ለልማት በጣም አስፈላጊ እየሆኑ መጥተዋል. ደህና ፣ ለተመቻቸ የአሲክ ተግባራት አጠቃቀም ፣ መጠቀም አለብዎት Async Iterators. የጃቫ ስክሪፕት ገንቢ ይህንን በደንብ ጠንቅቆ ማወቅ አለበት።

Skillbox ይመክራል፡

ምንጭ: hab.com

አስተያየት ያክሉ