Async و Await در جاوا اسکریپت – توضیح به زبان ساده + مثال و کد

۸۳۱ بازدید
آخرین به‌روزرسانی: ۲۴ اردیبهشت ۱۴۰۲
زمان مطالعه: ۱۳ دقیقه
Async و Await در جاوا اسکریپت – توضیح به زبان ساده + مثال و کد

در زبان برنامه نویسی جاوا اسکریپت، کلمات کلیدی «Async» و «Await» «سینتکسی» (Syntax) جدید هستند که به مدیریت عملیات ناهمزمانی کمک می‌کنند. در این مطلب آموزش از مجله فرادرس موضوع Async و Await در جاوا اسکریپت و ابعاد مختلف آن پوشش داده خواهد شد تا کاربران بتوانند در آخر عملیات ناهمزمانی را در جاوا اسکریپت یاد بگیرند.

 ناهمزمانی در جاوا اسکریپت چیست؟

زبان برنامه نویسی جاوا اسکریپت شامل عملیات خاصی است که ناهمزمان هستند، به این معنی که مقدار یا نتیجه تولید شده فوراً در دسترس نخواهد بود، این موضوع به نام جاوا اسکریپت ناهمزمان شناخته می‌شود.

قطعه کد زیر برای درک این موضوع است:

1function fetchDataFromApi() {
2  // Data fetching logic here
3  console.log(data);
4}
5
6fetchDataFromApi();
7console.log('Finished fetching data');

به دلیل ماهیت ناهمزمان تابع fetchDataFromApi  ، مفسر جاوا اسکریپت قبل از ادامه دستورات بعدی، منتظر تکمیل آن نمی‌ماند. در نتیجه، قبل از ثبت اطلاعات واقعی به‌دست‌آمده از API، فرایند Finished Fetching  را ثبت می‌کند. در بسیاری از شرایط، این عملیات مورد نظر نیست. خوشبختانه، می‌توان از ویژگی Async در جاوا اسکریپت استفاده کرد و منتظر این کلمات کلیدی ماند تا برنامه‌ قبل از ادامه، منتظر پایان عملیات ناهمزمان باشد. این ویژگی در «ES2017» به جاوا اسکریپت معرفی شد و توسط تمام مرورگرهای امروزی پشتیبانی می‌شود.

Async و Await در javascript

نحوه ایجاد تابع Async در جاوا اسکریپت

در این بخش از آموزش Async و Await در جاوا اسکریپت، منطق بازیابی اطلاعات تابع fetchDataFromApi  عمیق‌تر مورد بررسی قرار خواهد گرفت. fetchDataFromApi نمونه‌ای معمولی از عملیات ناهمزمان در جاوا اسکریپت است.

همان‌طور که در کدهای زیر نشان داده شده، می‌توان از Fetch API در جاوا اسکریپت برای رسیدن به این هدف استفاده کرد:

1function fetchDataFromApi() {
2  fetch('https://v2.jokeapi.dev/joke/Programming?type=single')
3    .then(res => res.json())
4    .then(json => console.log(json.joke));
5}
6
7fetchDataFromApi();
8console.log('Finished fetching data');

در سناریوی بالا، یک جوک برنامه نویسی از «JokeAPI» بازیابی شده است. پس از پاسخ API که با فرمت «JSON» خواهد بود، اطلاعات با استفاده از متد ‎ json()  استخراج می‌شوند و سپس لطیفه مربوط به کنسول خروجی داده خواهد شد. توجه به این نکته ضروری است که از آنجایی که JokeAPI نوعی API شخص ثالث محسوب می‌شود، پس نمی‌توان از کیفیت جوک‌هایی که برمی‌گرداند اطمینان حاصل کرد. اگر این کد در مرورگر یا در «نود جی اس» (Node.js) اجرا شود، امکان دارد که خروجی کنسول هنوز ترتیب درستی نداشته باشد.

جاوا اسکریپت ناهمگام

کلمه کلیدی Async در جاوا اسکریپت چیست؟

برای فعال کردن رفتار ناهمزمان در توابع جاوا اسکریپت، می‌توان از کلمه کلیدی Async قبل از کلمه کلیدی تابع استفاده کرد. برای مثال، اجازه دهید تابعی به نام fetchDataFromApi()‎  را در نظر بگیریم که داده‌ها را از یک API واکشی می‌کند.

همان‌طور که در ادامه نشان داده شده است، می‌توان با استفاده از کلمه کلیدی Async در جاوا اسکریپت آن را ناهمزمان کرد:

1async function fetchDataFromApi() {
2  fetch('https://v2.jokeapi.dev/joke/Programming?type=single')
3    .then(res => res.json())
4    .then(json => console.log(json.joke));
5}

وقتی تابعی به عنوان ناهمزمان علامت‌گذاری می‌شود، همیشه «وعده» (Promise) را برمی‌گرداند. می‌توان متد then()  را به فراخوانی تابع زنجیره زد تا اطمینان حاصل شود که ترتیب اجرا درست است. برای مثال، می‌توان تابع fetchDataFromApi()‎ را فراخوانی کرد و سپس متد then() را مانند کد زیر به آن زنجیره زد:

1fetchDataFromApi()
2  .then(() => {
3    console.log('Finished fetching data');
4  });

پس از اجرای کد، داده‌های واکشی شده و پیام 'Finished fetching data'  به عنوان خروجی دریافت خواهند شد و چیزی مشابه خروجی زیر را بیرون خواهد داد:

If Bill Gates had a dime for every time Windows crashed ... Oh wait, he does.
Finished fetching data

 

با این حال، سینتکس Promise در جاوا اسکریپت می‌تواند برای خواندن پیچیده و چالش‌برانگیز شود و اینجاست که Async و Await در جاوا اسکریپت مفید واقع خواهند شد. این ویژگی به کاربران امکان می‌دهد کدهای ناهمزمان بنویسند، در حالی که این کدها بیشتر شبیه کدهای هم‌زمان بوده و خواندن آن‌ها آسان‌تر است.

 Await در جاوا اسکریپت

کلمه کلیدی Await در جاوا اسکریپت

برای اینکه عملیات ناهمزمان را متوقف کنیم و منتظر نتیجه بمانیم، باید از کلمه کلیدی Await در مقابل آن عملیات در تابع استفاده کرد. سپس می‌توان نتایج این عملیات را به متغیرها اختصاص داد.

برای مثال، اجازه دهید تابع fetchDataFromApi()‎ را که قبلاً به عنوان ناهمزمان علامت‌گذاری کرده بودیم در نظر بگیریم. می‌توان از کلمه کلیدی Await برای توقف اجرا تا زمانی استفاده کرد که نتیجه فراخوانی API دریافت می‌شود و نتیجه را به یک متغیر اختصاص می‌دهد. سپس می‌توان از کلمه کلیدی Await دیگری برای توقف اجرا استفاده کرد و منتظر پاسخ JSON از تماس API ماند. در نهایت، می‌توان ویژگی joke  پاسخ JSON را به کنسول وارد کرد. در اینجا سینتکس تغییر تابع fetchDataFromApi()‎ با استفاده از کلمه کلیدی Await آمده است:

1async function fetchDataFromApi() {
2  const res = await fetch('https://v2.jokeapi.dev/joke/Programming?type=single');
3  const json = await res.json();
4  console.log(json.joke);
5}

همچنین باید منتظر نتیجه فراخوانی تابع fetchDataFromApi()‎ ماند که قطعه کد آن به صورت زیر است:

1await fetchDataFromApi();
2console.log('Finished fetching data');

برای اطمینان از صحیح بودن دستور اجرا، باید منتظر نتیجه فراخوانی تابع fetchDataFromApi()‎ بمانیم. می‌توان با استفاده از کلمه کلیدی Await دوباره به این هدف رسید. با این حال، اگر کاربر بخواهد از انتظار خارج از تابعی ناهمزمان در اسکریپت غیر ماژول استفاده کند، با خطای زیر مواجه خواهد شد.

Uncaught SyntaxError: await is only valid in async functions, async generators and modules

 

برای حل این مشکل، می‌توان کد فراخوانی را در تابعی دیگر قرار داد و آن را به عنوان ناهمزمان علامت‌گذاری کرد و سپس عمل فراخوانی را انجام داد. مثال زیر برای درک این موضوع مهم است:

1async function fetchDataFromApi() {
2  const res = await fetch('https://v2.jokeapi.dev/joke/Programming?type=single');
3  const json = await res.json();
4  console.log(json.joke);
5}
6
7async function init() {
8  await fetchDataFromApi();
9  console.log('Finished fetching data');
10}
11
12init();

اکنون اگر کدها اجرا شوند، همه چیز باید به ترتیب درست مانند خروجی زیر نمایش داده شود:

UDP is better in the COVID era since it avoids unnecessary handshakes.
Finished fetching data

ترتیب درست اجرا برای Async و Await در جاوا اسکریپت بسیار حائز اهمیت است.

توابع ناهمگام در جاوا اسکریپت

روش های مختلف تعریف توابع ناهمگام در جاوا اسکریپت

روش‌های مختلفی برای تعریف توابع ناهمگام، فراتر از دو اعلان تابع نام‌گذاری شده (با استفاده از کلمه کلیدی تابع و نام تابع) وجود دارند که در مثال قبلی ارائه شدند. همچنین می‌توان نشان داد که «عبارات تابع» (Function Expression)، «توابع پیکان» (Arrow Function) و «توابع ناشناس» (Anonymous Function) ناهمزمان هستند.

عبارت تابع در جاوا اسکریپت

عبارت تابع یا Function Expression به ایجاد تابع و انتساب آن به یک متغیر اشاره دارد. تابع ایجاد شده ناشناس است، به این معنی که نامی ندارد. مثال زیر این مفهوم را بیان می‌کند:

1const fetchDataFromApi = async function() {
2  const res = await fetch('https://v2.jokeapi.dev/joke/Programming?type=single');
3  const json = await res.json();
4  console.log(json.joke);
5}

این کد مانند مثال قبلی عمل می‌کند.

تابع پیکان ناهمگام

در «جاوا اسکریپت ES6»، توابع پیکان به عنوان نوعی جایگزین مختصر برای عبارات تابع معرفی شدند و همیشه ناشناس هستند.

سینتکس اصلی آن‌ها به صورت زیر است:

1(params) => { <function body> }

برای نشان دادن ناهمزمان بودن تابع پیکان، کلمه کلیدی Async را قبل از پرانتز باید نوشت. به عنوان مثال، به جای ایجاد یک تابع init  اضافی در کد قبلی، می‌توان کد موجود را در نوعی IIFE  قرار داد که این IIFE به‌ عنوان Async علامت‌گذاری شده است. مثال زیر این موضوع را بیان خواهد کرد:

1(async () => {
2  async function fetchDataFromApi() {
3    const res = await fetch('https://v2.jokeapi.dev/joke/Programming?type=single');
4    const json = await res.json();
5    console.log(json.joke);
6  }
7  await fetchDataFromApi();
8  console.log('Finished fetching data');
9})();

استفاده از توابع پیکان به جای عبارت‌ها یا اعلان‌های توابع، عمدتاً موضوعی با ترجیح شخصی است، با برخی تفاوت‌ها که باید از آن‌ها آگاه بود، مانند عمل Hoisting در جاوا اسکریپت و استفاده از کلمه کلیدی This در جاوا اسکریپت که در جاوا اسکریپت، Hoisting مکانیزمی است که در آن اعلان‌های تابع و متغیر در جاوا اسکریپت، قبل از اجرای کد به بالای محدوده مربوطه خود منتقل می‌شوند. این بدان معنی است که کاربر می‌تواند تابع یا متغیر را قبل از اعلان استفاده کند. با این حال، این ساز و کار با توابع پیکان کار نمی‌کند، که باید از آن آگاه بود.

علاوه بر این، در توابع معمولی، کلمه کلیدی This در جاوا اسکریپت به صورت پویا و بر اساس نحوه فراخوانی تابع محدود می‌شود، در حالی که توابع پیکان این اتصال خاص را ندارند. در عوض، آن‌ها This را از محدوده محصور به ارث می‌برند که گاهی اوقات می‌تواند منجر به رفتار غیرمنتظره شود.

کار با Async و Await در جاوا اسکریپت

Async و Await در جاوا اسکریپت بر اساس Promise

Async و Await در جاوا اسکریپت بیشتر شکل مختصری برای وعده‌ها محسوب می‌شوند. درک نحوه استفاده از «وعده‌ها در پس‌زمینه» (promises under the hood) بسیار مهم است.

توابع Async همیشه وعده‌ها را بازمی‌گردانند، حتی اگر به صراحت مشخص نشده باشند. مثال زیر این مفهوم را بیان می‌کند:

1async function echo(arg) {
2  return arg;
3}
4
5const res = echo(5);
6console.log(res);

خروجی این قطعه کد به صورت زیر خواهد بود:

Promise { : "fulfilled", : 5 }

 

حالت promise در جاوا اسکریپت می‌تواند یکی از سه گزینه ممکن، «در حال انتظار» (Pending)، «محقق شده» (Fulfilled) یا «رد شده» (Rejected) باشد. وعده در ابتدا در حالت معلق شروع می‌شود. در صورتی که عملیاتی که وعده بیانگر آن است موفقیت‌آمیز باشد، وعده محقق شده تلقی خواهد شد. بالعکس، در صورتی که عملیاتی که وعده بیانگر آن است موفقیت آمیز نباشد، وعده رد شده خواهد بود. هنگامی که وعده، محقق یا رد می‌شود، به عنوان «تسویه شده» (Settled) طبقه‌بندی خواهد شد، به این معنی که نمی‌تواند به هیچ حالت دیگری منتقل شود.

promise در JavaScript

وقتی که از کلمه کلیدی Await در تابع Async برای توقف اجرای آن استفاده می‌شود، کاری که در واقع انجام خواهد شد این است که کاربر منتظر وعده‌ای خواهد ماند که ممکن است آن وعده صریح یا ضمنی باشد و آن را در حالت محقق شده یا رد شده قرار دهد. با تکیه بر مثال بالا، کارهای گفته شده را می‌توان در مثال زیر بیان کرد.

1async function echo(arg) {
2  return arg;
3}
4
5async function getValue() {
6  const res = await echo(5);
7  console.log(res);
8}
9
10getValue();
11// 5

قطعه کد بالا نوعی تابع ناهمزمان در جاوا اسکریپت را نشان می‌دهد. تابع echo  با کلمه کلیدی Async اعلان می‌شود و یک وعده یا همان Promise را برمی‌گرداند. همچنین وقتی تابع getValue  فراخوانی می‌شود، از کلمه کلیدی Await استفاده می‌کند تا قبل از چاپ نتیجه در کنسول منتظر بماند تا وعده‌ای که توسط echo برگردانده می‌شود حل شود.

Promises نوعی ویژگی مهم در جاوا اسکریپت برای مدیریت عملیات ناهمزمان است. آن‌ها به‌طور گسترده توسط APIهای مرورگر جدیدتر مانند «Battery status API»، «Clipboard API»، «Fetch API» و «MediaDevices API» استفاده می‌شوند.

همچنین Node.js با تابع util.promisify  داخلی خود از وعده‌ها پشتیبانی می‌کند، که این تابع می‌تواند توابعی را که از callback  استفاده می‌کنند به وعده‌های بازگشتی تبدیل کند. علاوه بر این، با استفاده از «Node.js v10» یا نود جی اس ورژن ١٠ توابع در ماژول fs  می‌توانند مستقیماً وعده‌ها را برگردانند.

 

تغییر از Promise ها به Async و Await در جاوا اسکریپت

هر تابعی که یک وعده را بازمی‌گرداند، می‌تواند با Async و Await در جاوا اسکریپت استفاده شود، با این حال نباید همه‌چیز را در جاوا اسکریپت ناهمگام کرد و برای آن انتظار کشید، زیرا این نوع کد نویسی دارای جنبه‌های منفی است. این جنبه‌های منفی در رسیدگی به باگ‌ها خودشان را به‌خوبی نشان می‌دهند.

قبلاً بیان شد که چگونه می‌توان تماس واکشی مبتنی بر وعده را تغییر داد تا بتوان با Async و Await در جاوا اسکریپت کار کرد. در اینجا مثالی دیگر برای این کار بررسی خواهد شد. در مثال زیر نوعی تابع برای دریافت محتویات فایلی با استفاده از API مبتنی بر وعده Node و روش readFile  آوده شده است. قطعه کد این کار با استفاده از Promise.then()‎  به صورت زیر خواهد بود:

1const { promises: fs } = require('fs');
2
3const getFileContents = function(fileName) {
4  return fs.readFile(fileName, enc)
5}
6
7getFileContents('myFile.md', 'utf-8')
8  .then((contents) => {
9    console.log(contents);
10  });

حال همان قطعه کد با استفاده از Async و Await در جاوا اسکریپت به صورت زیر خواهد بود:

1import { readFile } from 'node:fs/promises';
2
3const getFileContents = function(fileName, enc) {
4  return readFile(fileName, enc)
5}
6
7const contents = await getFileContents('myFile.md', 'utf-8');
8console.log(contents);

نکته: کدهای بالا در واقع استفاده از نوعی ویژگی به نام «Top-Level-Await» به معنای انتظار سطح بالا، است که فقط در ماژول‌های ES وجود دارد. برای اجرای این کد، باید فایل را به صورت Index.mjs ذخیره و از نسخه ١٤.٨ به بالای نود‌جی‌اس استفاده کرد. اگر چه این‌ها نمونه‌های ساده‌ای هستند، اما سینتکس Async و Await در جاوا اسکریپت چندان هم دشوار نیست.

برنامه ناهمگام در JS

مدیریت خطا در جاوا اسکریپت ناهمگام چگونه است؟

توابع Async راه‌های مختلفی را برای رسیدگی به خطاها ارائه می‌دهند. یکی از متداول‌ترین راه‌ها استفاده از بلوک «try...catch»  برای کار با عملیات ناهمزمان و گرفتن هر گونه خطای رخ داده است.

در مثال زیر، باید توجه کرد که چگونه URL به چیزی که وجود ندارد تغییر یافته است.

1sync function fetchDataFromApi() {
2  try {
3    const res = await fetch('https://non-existent-url.dev');
4    const json = await res.json();
5    console.log(json.joke);
6  } catch (error) {
7    // Handle the error here in whichever way you like
8    console.log('Something went wrong!');
9    console.warn(error)
10  }
11}
12
13await fetchDataFromApi();
14console.log('Finished fetching data');

خروجی این مثال به صورت زیر خواهد بود.

Something went wrong!
TypeError: fetch failed
    ...
    cause: Error: getaddrinfo ENOTFOUND non-existent-url.dev
Finished fetching data

 

اگر عملیات واکشی ناموفق باشد، متد «رد وعده» فراخوانی می‌شود و کلمه کلیدی Await آن وعده رد شده کنترل نشده را به نوعی خطای قابل دریافت تبدیل می‌کند. در این مورد، پیام Something went wrong  همراه با «خطای نوع» (TypeError) که نشان می‌دهد واکشی ناموفق است به کنسول وارد می‌شود. در نهایت، کنسول Finished Fetching Data  را ثبت می‌کند. بلوک try...catch  به کاربر این امکان را می‌دهد که خطاها را به خوبی مدیریت کند، بدون اینکه باعث از کار افتادن برنامه شود.

روش فوق برای رسیدگی به خطاها در توابع همگام چند مشکل دارد. اولاً، می‌تواند به کدهای شلوغ و پیچیده منجر شود. برای مثال در ساخت برنامه «CRUD» با عملکردهای جداگانه برای ایجاد، خواندن و به‌روزرسانی عملیات نیاز خواهد بود که هر کدام شامل یک فراخوانی API ناهمزمان است و هر تماس باید در بلوک try...catch منحصربه‌فرد خودش نوشته شود که این کار منجر به افزایش چشم‌گیر کدها خواهد شد. موضوع دیگر این است که در صورت عدم استفاده از کلمه کلیدی Await، رد وعده لغو می‌شود. مثال زیر برای درک این موضوع مهم است:

1import { readFile } from 'node:fs/promises';
2
3const getFileContents = function(fileName, enc) {
4  try {
5    return readFile(fileName, enc)
6  } catch (error) {
7    console.log('Something went wrong!');
8    console.warn(error)
9  }
10}
11
12const contents = await getFileContents('this-file-does-not-exist.md', 'utf-8');
13console.log(contents);

خروجی این مثال به صورت زیر است:

node:internal/process/esm_loader:91
    internalBinding('errors').triggerUncaughtException(
                              ^
[Error: ENOENT: no such file or directory, open 'this-file-does-not-exist.md'] {
  errno: -2,
  code: 'ENOENT',
  syscall: 'open',
  path: 'this-file-does-not-exist.md'
}

 

بر خلاف کلمه کلیدی Await، کلمه کلیدی «Return»، رد وعده‌ها را به خطاهای قابل دریافت تبدیل نمی‌کند.

استفاده از Catch برای مدیریت رد وعده

هر تابعی که وعده‌ای را برمی‌گرداند می‌تواند از متد catch()  برای رسیدگی به رد وعده‌هایی استفاده کند که ممکن است رخ دهند. این رویکرد مختصرتر از استفاده از بلوک‌های try...catch برای هر تماس ناهمزمان است.

با پیاده‌سازی متد catch() در مثال بالا، می‌توان به‌خوبی خطا را مطابق قطعه کد زیر مدیریت کرد:

1const contents = await getFileContents('this-file-does-not-exist.md', 'utf-8')
2  .catch((error) => {
3    console.log('Something went wrong!');
4    console.warn(error);
5  });
6console.log(contents);

خروجی این قطعه کد به صورت زیر خواهد بود:

Something went wrong!
[Error: ENOENT: no such file or directory, open 'this-file-does-not-exist.md'] {
  errno: -2,
  code: 'ENOENT',
  syscall: 'open',
  path: 'this-file-does-not-exist.md'
}
undefined

 

در این رابطه، «والری کارپوف» (Valeri Karpov) با توجه به اینکه کدام رویکرد باید استفاده شود، استفاده از بلوک‌های try...catch را برای بازیابی خطاهای مورد انتظار در توابع Async پیشنهاد کرد. همچنین برای خطاهای غیرمنتظره، توصیه می‌شود با افزودن catch() به تابع فراخوانی، آن را مدیریت کنیم.

عملیات ناهمزمان در JavaScript

انجام همزمان عملیات ناهمزمان در جاوا اسکریپت

وقتی از کلمه کلیدی Await برای منتظر ماندن برای تکمیل عملیات ناهمزمان استفاده می‌شود، مفسر جاوا اسکریپت اجرا را تا زمانی متوقف می‌کند که عملیات کامل شود. با این حال، مواقعی وجود دارد که این رفتار مناسب نیست.

کد زیر برای درک این موضوع مهم است:

1(async () => {
2  async function getStarCount(repo){
3    const repoData = await fetch(repo);
4    const repoJson = await repoData.json()
5    return repoJson.stargazers_count;
6  }
7
8  const reactStars = await getStarCount('https://api.github.com/repos/facebook/react');
9  const vueStars = await getStarCount('https://api.github.com/repos/vuejs/core');
10  console.log(`React has ${reactStars} stars, whereas Vue has ${vueStars} stars`)
11})();

مثال بالا در حال انجام دو تماس API برای دریافت تعداد ستاره‌های «GitHub» برای دو فریمورک «React» و «Vue» است. این کد به خوبی کار می‌کند و نیازی به انتظار برای اولین وعده حل شده قبل از درخواست واکشی دوم وجود ندارد، اما اگر درخواست‌های زیادی وجود داشته باشند، اجرای کد با مشکل مواجه خواهد شد.

برای حل این مشكل، می‌توان از Promise.all  استفاده کرد. Promise.all در جاوا اسکریپت مجموعه‌ای از وعده‌ها را می‌گیرد و منتظر می‌ماند تا همه وعده‌ها حل شوند یا هر یک از آن‌ها رد شوند که قطعه کد زیر برای بیان این موضوع است:

1(async () => {
2  async function getStarCount(repo){
3    // As before
4  }
5
6  const reactPromise = getStarCount('https://api.github.com/repos/facebook/react');
7  const vuePromise = getStarCount('https://api.github.com/repos/vuejs/core');
8  const [reactStars, vueStars] = await Promise.all([reactPromise, vuePromise]);
9
10  console.log(`React has ${reactStars} stars, whereas Vue has ${vueStars} stars`);
11})();

استفاده از انتظار ناهمزمان در حلقه های همزمان

استفاده از انتظار ناهمزمان در حلقه‌های همزمان ممکن است زمانی مورد نیاز باشد که لازم باشد تابعی ناهمزمان را در حلقه همزمان فراخوانی کنیم.

مثال زیر این موضوع را بیان می‌کند:

1// Return promise which resolves after specified no. of milliseconds
2const sleep = (ms) => new Promise(resolve => setTimeout(resolve, ms));
3
4async function process(array) {
5  array.forEach(async (el) => {
6    await sleep(el); // we cannot await promise here
7    console.log(el);
8  });
9}
10
11const arr = [3000, 1000, 2000];
12process(arr);

این عمل آن‌طور که انتظار می‌رود کار نمی‌کند، زیرا «حلقه forEach در جاوا اسکریپت» تابع را بدون انتظار برای تکمیل آن فراخوانی می‌کند و در کنسول، خروجی زیر ثبت خواهد شد.

1000
2000
3000

 

همین امر در مورد سایر متدهای آرایه در جاوا اسکریپت از جمله «نقشه» (Map)، «فیلتر» (Filter) و «کاهش» (Reduce) نیز صدق می‌کند. خوشبختانه، «ES2018» «تکرارکننده‌های» (Iterators) ناهمزمان را معرفی کرد که مانند تکرارکننده‌های معمولی رفتار می‌کنند، اما وعده‌ای را از متد next()  خود بازمی‌گردانند. این ویژگی امکان استفاده از تابع Await را در آن‌ها فراهم می‌کند. حال در زیر برای درک بهتر کد بالا با استفاده از یکی از این تکرارکننده‌های جدید (در اینجا تکرار کننده for…of  ) بازنویسی خواهد شد:

1async function process(array) {
2  for (el of array) {
3    await sleep(el);
4    console.log(el);
5  };
6}

اکنون تابع process  همه چیز را به ترتیب صحیح در خروجی نشان می‌دهد:

3000
1000
2000

مانند مثال قبلی، وقتی از کلمه کلیدی Await در حلقه استفاده می‌شود، باعث خواهد شد که حلقه منتظر بماند تا هر کار جداگانه قبل از رفتن به کار بعدی تکمیل شود. این کار در رابطه با موضوع استفاده از Async و Await در جاوا اسکریپت می‌تواند برنامه را کند و ناکارآمد کند، به خصوص اگر تعداد وظایف زیادی برای پردازش وجود داشته باشد.

در عوض، بهتر است تمام وعده‌ها را یک‌باره خارج از حلقه ایجاد و سپس از Promise.all()‎  استفاده کرد تا قبل از ادامه، منتظر ماند تا همه آن‌ها تکمیل شوند. این به برنامه کمک می‌کند تا از مسدود کردن حلقه رویداد جلوگیری کرده و عملکرد کلی برنامه را بهبود بخشد. حتی نوعی قانون «ESLint» برای این مسئله وجود دارد که به کاربر در مورد این رفتار هشدار می‌دهد.

انتظار سطح بالا در جاوا اسکریپت

اگر از کلمه کلیدی Await خارج از تابع Async در جاوا اسکریپت استفاده شود، خطای زیر دریافت خواهد شد.

Uncaught SyntaxError: await is only valid in async functions, async generators and modules

 

قطعه کد زیر این موضوع را نشان می‌دهد:

1const ms = await Promise.resolve('Hello, World!');
2console.log(msg)

این مشکل با معرفی نوعی ویژگی به نام انتظار سطح بالا در «ECMAScript 2022» برطرف شده و از نسخه ۱۴.۸ به بعد نود جی اس در دسترس است.

انتظار سطح بالا در JavaScript

«انتظار سطح بالا» (Top-level Await) به کاربر امکان می‌دهد از کلمه کلیدی Await در سطح بالای کد خود استفاده کند. با این حال، این ویژگی فقط در ماژول‌های ES معتبر است. برای استفاده از آن در مرورگر، می‌توان یک فایل index.js ایجاد کرد و آن را با تگ script  مانند زیر در صفحه قرار داد.

1<script src="index.js" type="module"></script>

در نود جی اس، می‌توان فایلی را به عنوان ماژول «ES» ساخت و آن را پسوند «mjs.» ذخیره کرد. بعد می‌توان اعلان آن را با دستور نود جی اس زیر انجام داد:

1node index.mjs

همچنین، می‌توان فیلد type  را روی module  در فایل «package.json» مانند کدهای زیر قرار داد.

1{
2  "name": "myapp",
3  "type": "module",
4  ...
5}

انتظار سطح بالا در مسائل مربوط به Async و Await در جاوا اسکریپت با «ایمپورت‌های پویا» (Dynamic Imports) به خوبی کار می‌کند و این به کاربر امکان می‌دهد ماژول‌های ES را به صورت ناهمزمان بارگیری کند. همچنین کاربر می‌تواند از نوعی عبارت تابع مانند برای وارد کردن پویای ماژول بر اساس برخی شرایط استفاده کند. مثال زیر برای بیان این موضوع است.

1const locale = 'DE';
2
3const { default: greet } = await import(
4  `${ locale === 'DE' ?
5      './de.js' :
6      './en.js'
7  }`
8);
9
10greet();
11// Outputs "Hello" or "Guten Tag" depending on the value of the locale variable

ویژگی فوق می‌تواند به ویژه برای «بارگذاری با تاخیر» (Lazy Load) در ارتباط با فریمورک‌هایی مانند «React» و «Vue» مفید باشد. با استفاده از ویژگی وارد کردن پویا، می‌توان با کاهش مقدار کدی که در ابتدا باید بارگذاری شود، زمان تعاملی شدن برنامه را بهبود بخشید، به این معنی که کاربران می‌توانند زودتر از آن برنامه استفاده کنند.

سخن پایانی

در این مطلب از مجله فرادرس، نحوه مدیریت جریان کنترل برنامه با استفاده از Async/Await بررسی شد. همچنین سینتکس، نحوه عملکرد Async و Await در جاوا اسکریپت، نحوه رسیدگی به خطاها و برخی از کارهایی که باید در این نوع کدنویسی از آن اجتناب شود، مورد بررسی قرار گرفتند. برنامه‌نویسی ناهمگام در جاوا اسکریپت برای افراد تازه وارد می‌تواند مسئله‌ای چالش‌برانگیز باشد و یادگیری روش‌های شرح داده شده در این مطلب، در این زمینه بسیار حائز اهمیت است.

بر اساس رای ۱۰ نفر
آیا این مطلب برای شما مفید بود؟
اگر بازخوردی درباره این مطلب دارید یا پرسشی دارید که بدون پاسخ مانده است، آن را از طریق بخش نظرات مطرح کنید.
منابع:
SitePoint
نظر شما چیست؟

نشانی ایمیل شما منتشر نخواهد شد. بخش‌های موردنیاز علامت‌گذاری شده‌اند *