جاوااسکریپتمقدماتی۷ دقیقه مطالعهECMAScript 2023+

آشنایی با برنامه‌نویسی ناهمگم

کار با عملیاتی که نتیجه‌شان دیرتر می‌رسد؛ setTimeout، وعده‌ها (Promise) و ساختار async/await برای خواندن کد ناهمگم به‌شکل همگم.

آنچه در این درس می‌آموزید

  • بفهمید چرا عملیاتی مثل درخواست شبکه نمی‌توانند اجرای برنامه را متوقف کنند
  • نتیجهٔ عملیات تأخیری را با Promise و then دریافت کنید
  • با async/await کد ناهمگم را به شکل خوانا بنویسید و خطاهایش را مدیریت کنید
نویسنده: تیم داکنامه · بازبینی: ۲۰۲۶-۱۰-۳۰
فهرست مطالب
پیش‌نیاز: رویدادها در DOM

تا اینجا کدهای ما همیشه خط‌به‌خط و بلافاصله اجرا می‌شدند. اما بخش بزرگی از کارهای برنامهٔ وب دیر جواب می‌دهد: دانلود داده از سرور، خواندن یک فایل یا انتظار برای تایپ کاربر. اگر برنامه برای هر دانلودی چند ثانیه می‌ایستاد و کل صفحه منجمد می‌شد، تجربهٔ کاربری فاجعه می‌شد. راه‌حل جاوااسکریپت «برنامه‌نویسی ناهمگم» (Asynchronous Programming) است: درخواست را بفرست، برو سراغ کار بعدی، و وقتی جواب رسید بقیهٔ کار را ادامه بده. این درس کمی از درس‌های قبل سخت‌تر است؛ اگر در نخستین خواندن همه‌چیز جا نیفتاد، نگران نباشید.

شمارش رویدادها؛ چرا ترتیب عوض می‌شود؟

ساده‌ترین ابزار ناهمگم، setTimeout است: تابعی را بعد از گذشت مدتی بر حسب میلی‌ثانیه اجرا می‌کند. حدس بزنید این برنامه چه چاپ می‌کند:

order.js
console.log("first");
setTimeout(() => {
  console.log("third");
}, 100);
console.log("second");
خروجی کد

first second third

حالا هشدار: این برنامه نه ۱۰۰ میلی‌ثانیه صبر می‌کند و نه به ترتیب نوشتن اجرا می‌شود. setTimeout فقط یک کار را زمان‌بندی می‌کند و برمی‌گردد؛ خط «second» بلافاصله اجرا می‌شود و پس از خالی‌شدن دست مرورگر یا Node از کارهای فوری، ۱۰۰ میلی‌ثانیه بعد، پیام «third» چاپ می‌شود. این مکانیزم «حلقهٔ رویداد» (Event Loop) نام دارد: یک صف از کارهای تأخیری که فقط وقتی دست آزاد است سراغشان می‌رود.

این یعنی قانون طلایی: کد ناهمگم به‌ترتیب زمانیِ پایان اجرا می‌شود، نه ترتیب نوشتن. و یعنی نمی‌توان نتیجهٔ یک کار تأخیری را در خط بعدی به‌سادگی گرفت؛ به ابزاری نیاز داریم که «وقتی آماده شد، این کار را بکن» را تمیز بیان کند.

وعده‌ها؛ Promise

«وعده» (Promise) شیئی است که نمایندهٔ نتیجه‌ای است که هنوز نیامده. مثل رسیدِ سفارش: هنوز کالا دستتان نرسیده، اما یک شیء در دست دارید که می‌گوید «به‌زودی یا نتیجه می‌آید یا خطا». هر Promise یکی از سه حالت را دارد: در انتظار (Pending)، موفق (Fulfilled) یا ردشده (Rejected).

promise.js
const promise = new Promise((resolve) => {
  setTimeout(() => resolve("Work done"), 50);
});

promise.then((message) => console.log(message));
console.log("Waiting...");
خروجی کد

Waiting... Work done

توضیح خط‌به‌خط:

  1. سازندهٔ Promise تابعی می‌گیرد که بلافاصله اجرا می‌شود؛ درون آن کارِ تأخیری انجام می‌شود.
  2. تابع resolve را مرورگر می‌دهد؛ صدا زدنش یعنی «وعده عمل شد» و مقدارش نتیجه است. در سوی مقابل، reject برای اعلام خطا است.
  3. then می‌گوید «وقتی نتیجه رسید این تابع را اجرا کن». خودش بلافاصله برمی‌گردد و برنامه به خط بعد می‌رود؛ برای همین «Waiting...» اول چاپ می‌شود.

سازندگان کتابخانه‌های مدرن تقریباً همه‌جا به‌جای توابع قدیمیِ «پاسخ به‌محض رسید» (Callback) وعده برمی‌گردانند؛ Promise زبان مشترک ناهمگمی مدرن است.

async و await

زنجیرهٔ then در کارهای طولانی به‌هم‌ریخته می‌شود. ساختار async/await از سال ۲۰۱۷ اجازه می‌دهد کد ناهمگم را مثل کد معمولی، خط‌به‌خط و خوانا بنویسیم:

await.js
function wait(ms) {
  return new Promise((resolve) => setTimeout(resolve, ms));
}

async function main() {
  console.log("Start");
  await wait(50);
  console.log("After the wait");
}

main();
خروجی کد

Start After the wait

دو کلیدواژه جفت‌اند:

  • async پیش از تعریف تابع یعنی «این تابع وعده برمی‌گرداند و حق دارد از await استفاده کند».
  • await یعنی «منتظر وعده بمان و وقتی نتیجه رسید در همان متغیر بگذار». اجرای تابع از این خط موقتاً متوقف می‌شود ولی برنامهٔ کلی متوقف نمی‌شود؛ بقیهٔ صفحه به کارش ادامه می‌دهد.

همین دو کلیدواژه کد را از زنجیرهٔ then به شکلی می‌آورد که ذهن ما ساده‌ترش را می‌فهمد.

مدیریت خطا در کد ناهمگم

کار تأخیری ممکن است شکست بخورد: شبکه قطع شود یا سرور خطا بدهد. وقتی تابعی reject می‌شود، await همان‌جا خطا پرتاب می‌کند و می‌توانید مثل کد همگم با try/catch بگیریدش:

error.js
async function risky() {
  throw new Error("Something went wrong");
}

risky().catch((error) => console.log("Caught:", error.message));
خروجی کد

Caught: Something went wrong

تابع async که با throw شکست بخورد، وعده‌ای ردشده برمی‌گرداند؛ .catch تابع دریافت خطا را وصل می‌کند. داخل خود تابعِ async نیز try/catch در اطراف await کار می‌کند. خطاهای ناهمگم را هرگز بی‌رسید رها نکنید؛ برنامه بی‌صدا از کار می‌افتد و کاربر فقط یک چرخ دندهٔ همیشگی می‌بیند.

درخواست شبکه با fetch

همهٔ این ابزارها برای یک هدف روزمره ساخته شده‌اند: گرفتن داده از سرور. متد fetch وعده‌ای برای پاسخ HTTP می‌دهد و خود پاسخ هم وعدهٔ دیگری برای بدنهٔ JSON دارد:

load-user.js
async function loadUser() {
  const response = await fetch("https://api.example.com/user/1");
  const data = await response.json();
  console.log(data.name);
}

دو await پشت سر هم دو انتظار را مدیریت می‌کنند: نخست رسیدن سرآیندهای پاسخ، بعد تبدیل بدنه به شیء جاوااسکریپت — همان اشیاء و آرایه‌هایی که در درس‌های قبلی خواندیم. کد واقعی را با یک سرور واقعی در پروژه‌های بعدی تمرین خواهید کرد؛ الگوی کلی همین است.

تمرین‌ها

تمرین

تمرین ۱ — پیام تأخیری

تابعی به نام waitAndPrint بنویسید که یک رشته بگیرد و پس از ۵۰ میلی‌ثانیه آن را چاپ کند؛ این تابع باید یک وعده برگرداند تا بتوانید آن را با await یا then به‌کار ببرید. سپس آن را با پیام «Hello, delayed!» فراخوانی کنید.

js
// Write your code here
نمایش راه‌حل
js
function waitAndPrint(message) {
  return new Promise((resolve) => {
    setTimeout(() => {
      console.log(message);
      resolve();
    }, 50);
  });
}

waitAndPrint("Hello, delayed!");
خروجی کد

Hello, delayed!

::: :::

خلاصه

  • کارهای تأخیری مثل درخواست شبکه نباید اجرای برنامه را متوقف کنند؛ نتیجه‌شان بعداً در صف رویداد رسیدگی می‌شود.
  • setTimeout کار را زمان‌بندی می‌کند و کد ناهمگم به ترتیب زمانی پایان اجرا می‌شود، نه ترتیب نوشتن.
  • Promise نمایندهٔ نتیجه‌ای آینده است با سه حالت: در انتظار، موفق و ردشده؛ then به موفقیت و catch به شکست رسیدگی می‌کند.
  • async/await کد ناهمگم را خط‌به‌خط و خوانا می‌کند و خطایش با try/catch مدیریت می‌شود.
  • fetch پاسخ شبکه را به‌شکل وعده می‌دهد و ستون کار با APIها در برنامه‌های واقعی است.

و با این درس، مسیر مقدماتی جاوااسکریپت کامل شد: از متغیرها تا برنامه‌ای که صفحه را می‌بیند، به کاربر گوش می‌دهد و با دنیای بیرون حرف می‌زند. قدم بعدی پیشنهاد ما ساخت یک پروژهٔ کوچک واقعی است — مثلاً فهرست کارها با ذخیره در مرورگر — تا همهٔ این مفاهیم را در یک کد واحد به هم بدوزید.

منابع و ارجاع‌ها

درس‌های مرتبط