درک کامل Async/Await در JavaScript

JavaScript Async/Await - برنامه‌نویسی ناهمزمان

در دنیای JavaScript، یکی از مهم‌ترین مفاهیم در برنامه‌نویسی مدرن، مدیریت کدهای ناهمزمان (Asynchronous Code) است. با معرفی async/await در ES2017، نوشتن کدهای ناهمزمان بسیار ساده‌تر و خواناتر شد.

۱. چرا به async/await نیاز داریم؟

قبل از async/await، توسعه‌دهندگان از callback و سپس از Promise برای مدیریت عملیات ناهمزمان استفاده می‌کردند. اما هر دو روش مشکلاتی داشتند:

  • Callback Hell: تودرتو شدن بیش از حد توابع
  • Promise Chain طولانی: سخت شدن خوانایی کد

به مثال زیر نگاه کنید:

fetchUser()
  .then(user => fetchPosts(user.id))
  .then(posts => fetchComments(posts[0].id))
  .then(comments => console.log(comments))
  .catch(error => console.error(error));

این کد کار می‌کند، اما خواندن و دیباگ آن سخت است. با استفاده از async/await می‌توان همین منطق را به شکل ساده و قابل‌درک نوشت.

۲. async و await دقیقاً چه هستند؟

دو کلمه‌ی کلیدی جدید:

💡
به زبان ساده:
  • async: قبل از یک تابع نوشته می‌شود تا آن تابع را غیرهمزمان کند
  • await: فقط داخل توابع async قابل استفاده است و باعث توقف اجرا تا دریافت نتیجه می‌شود

۳. مثال ساده از async/await

async function fetchData() {
  const response = await fetch("https://jsonplaceholder.typicode.com/posts/1");
  const data = await response.json();
  console.log(data);
}

fetchData();
🔹
توضیح مرحله‌به‌مرحله:
  1. تابع fetchData با async تعریف شده، پس همیشه یک Promise برمی‌گرداند
  2. کلمه‌ی await قبل از fetch باعث توقف اجرا تا دریافت پاسخ می‌شود
  3. سپس await response.json() تا آماده شدن داده‌ها منتظر می‌ماند
  4. در نهایت داده چاپ می‌شود، بدون نیاز به .then()

۴. async همیشه Promise برمی‌گرداند

حتی اگر داخل تابع async هیچ Promise وجود نداشته باشد، خروجی آن خودبه‌خود در قالب Promise بازگردانده می‌شود:

async function sayHello() {
  return "سلام دنیا!";
}

sayHello().then(msg => console.log(msg)); // سلام دنیا!

در واقع این دو برابرند:

async function f1() {
  return "Hi";
}

function f2() {
  return Promise.resolve("Hi");
}

۵. مدیریت خطاها در async/await

در Promiseها برای مدیریت خطا از .catch() استفاده می‌کنیم. اما در async/await می‌توانیم از try...catch بهره ببریم:

async function getUserData() {
  try {
    const response = await fetch("https://jsonplaceholder.typicode.com/users/1");
    const user = await response.json();
    console.log(user);
  } catch (error) {
    console.error("خطا در دریافت اطلاعات:", error);
  }
}
این روش خطاها را به‌صورت تمیزتر و قابل‌فهم‌تر مدیریت می‌کند.

۶. اجرای همزمان چند Promise با Promise.all

گاهی نیاز داریم چند عملیات را به‌صورت همزمان انجام دهیم. اگر از await پشت‌سر‌هم استفاده کنیم، هر خط منتظر قبلی می‌ماند — که باعث کاهش کارایی می‌شود.

async function getData() {
  const [users, posts] = await Promise.all([
    fetch("https://jsonplaceholder.typicode.com/users").then(res => res.json()),
    fetch("https://jsonplaceholder.typicode.com/posts").then(res => res.json())
  ]);
  
  console.log(users.length, "کاربر");
  console.log(posts.length, "پست");
}

getData();
🔹
در اینجا هر دو درخواست به‌صورت همزمان ارسال می‌شوند، و await منتظر نتیجه‌ی هر دو است. این روش بسیار سریع‌تر از اجرای ترتیبی است.

۷. تفاوت async/await با Promise

ویژگی Promise Async/Await
سینتکس مبتنی بر .then() و .catch() شبیه کد همزمان با try/catch
خوانایی نسبتاً پیچیده در زنجیره‌ها بسیار خوانا و تمیز
خطاها با .catch() مدیریت می‌شود با try...catch مدیریت می‌شود
کنترل جریان سخت‌تر در پروژه‌های بزرگ ساده‌تر و ساختاریافته‌تر

۸. خطاهای رایج در استفاده از async/await

❌ ۱. استفاده از await خارج از تابع async

const data = await fetchData(); // خطا

✅ باید داخل تابع async باشد:

async function run() {
  const data = await fetchData();
}

❌ ۲. فراموش کردن await قبل از Promise

const res = fetch("url");
const data = res.json(); // خطا

✅ نسخه‌ی درست:

const res = await fetch("url");
const data = await res.json();
⚠️
توجه: فراموش کردن await یکی از رایج‌ترین اشتباهات است که باعث باگ‌های سخت می‌شود!

۹. نکته‌ی مهم درباره حلقه‌ها

اگر در یک حلقه از await استفاده کنید، هر مرحله منتظر مرحله‌ی قبل می‌ماند — یعنی اجرای ترتیبی دارد:

for (let id of [1, 2, 3]) {
  await fetchUser(id); // ترتیبی: 1، سپس 2، سپس 3
}

اگر عملیات‌ها مستقل هستند و به ترتیب خاصی نیاز ندارند، بهتر است از Promise.all استفاده کنید:

await Promise.all([1, 2, 3].map(id => fetchUser(id))); // همزمان

۱۰. جمع‌بندی

Async/Await ابزاری قدرتمند و ساده برای مدیریت کدهای ناهمزمان در JavaScript است. با استفاده از آن می‌توانید:

  • منطق‌های پیچیده‌ی Promise را به کدهای خوانا تبدیل کنید
  • خطاها را راحت‌تر مدیریت کنید
  • عملکرد برنامه را بهبود دهید

"بهترین راه یادگیری async/await تمرین در پروژه‌های واقعی است؛ مثلاً در فراخوانی API، خواندن فایل‌ها (در Node.js)، یا ارتباط با دیتابیس‌ها."

- توصیه‌های JavaScript Community
نکات پایانی:
  • همیشه از try/catch برای کنترل خطاها استفاده کنید
  • از Promise.all برای افزایش کارایی بهره ببرید
  • await را تنها زمانی استفاده کنید که واقعاً نیاز به توقف اجرا دارید
🎉
تبریک! حالا با async/await در JavaScript آشنا شدید. تمرین کنید و در پروژه‌های واقعی استفاده کنید!
ترنم

ترنم کمالی پناه

توسعه‌دهنده فرانت‌اند و طراح UI/UX. علاقه‌مند به JavaScript و تکنولوژی‌های مدرن وب. در این بلاگ تجربیات و دانش خود را به اشتراک می‌گذارم.