MATIN MOLKARA
SERIES: JavaScript — PART 59 OF 108

59- آموزش جاوااسکریپت | ساخت کتابخانه EasyHTTP V3: بهینه‌سازی با Async/Await

در این بخش، کتابخانه EasyHTTP V2 را بازنویسی کرده و با حذف ساختارهای پیچیده Promises، آن را به قدرت Async/Await مجهز می‌کنیم. این تغییرات کدهای ما را فشرده‌تر و خواناتر کرده و مدیریت درخواست‌های HTTP را بسیار ساده‌تر و حرفه‌ای‌تر می‌کند.

آموزش گام‌به‌گام توسعه EasyHTTP نسخه ۳ با Async/Await

در نسخه دوم کتابخانه EasyHTTP، درخواست‌ها با Fetch API و Promise پیاده‌سازی شدند. در این نسخه، همان متدها را با استفاده از async/await بازنویسی می‌کنیم.

هدف اصلی این تغییر:

  • حذف new Promiseهای اضافی
  • حذف زنجیره‌های .then()
  • خواناتر شدن کدها
  • نزدیک‌تر شدن ساختار کدهای asynchronous به کدهای synchronous

در این نسخه، متدهای زیر را بازنویسی می‌کنیم:

  • get
  • post
  • put
  • delete

1. آماده‌سازی نسخه سوم

ابتدا فایل کتابخانه نسخه دوم را کپی کرده و نام آن را به نسخه سوم تغییر می‌دهیم:

easyhttp2.js → easyhttp3.js

سپس در فایل index.html مسیر فایل جدید را وارد می‌کنیم:

<script src="easyhttp3.js"></script>
<script src="app.js"></script>

فایل app.js می‌تواند بدون تغییر باقی بماند، چون متدهای کتابخانه همان نام‌های قبلی را حفظ می‌کنند.

ساختار ساده فایل HTML می‌تواند به این شکل باشد:

<!DOCTYPE html>
<html lang="en">
<head>
  <meta charset="UTF-8">
  <meta name="viewport" content="width=device-width, initial-scale=1.0">
  <title>EasyHTTP Version 3</title>
</head>
<body>
  <script src="easyhttp3.js"></script>
  <script src="app.js"></script>
</body>
</html>

2. تفاوت ساختار نسخه دوم و سوم

در نسخه دوم، متد get تقریباً به این شکل نوشته شده بود:

get(url) {
  return new Promise((resolve, reject) => {
    fetch(url)
      .then((res) => res.json())
      .then((data) => resolve(data))
      .catch((err) => reject(err));
  });
}

در این ساختار، یک Promise جدید می‌سازیم و Promise مربوط به fetch را داخل آن قرار می‌دهیم.

اما در نسخه سوم، با اضافه کردن کلمه async به متد، خود تابع به‌صورت خودکار یک Promise برمی‌گرداند. بنابراین دیگر نیازی به نوشتن new Promise نداریم.


3. بازنویسی متد GET

ابتدا کلاس EasyHTTP را ایجاد می‌کنیم:

class EasyHTTP {
  // متدها در این قسمت قرار می‌گیرند
}

سپس متد get را به شکل یک تابع asynchronous تعریف می‌کنیم:

class EasyHTTP {
  async get(url) {
    const response = await fetch(url);
    const resData = await response.json();

    return resData;
  }
}

بررسی مرحله‌به‌مرحله کد

مرحله اول: ارسال درخواست

const response = await fetch(url);

متد fetch درخواست را ارسال می‌کند و یک Promise برمی‌گرداند.
کلمه await باعث می‌شود اجرای ادامه تابع تا زمان دریافت پاسخ متوقف شود.

متغیر response در این مرحله هنوز داده نهایی نیست؛ بلکه یک شیء از نوع Response است.

مرحله دوم: تبدیل پاسخ به JSON

const resData = await response.json();

متد response.json() محتوای پاسخ را می‌خواند و آن را به داده قابل استفاده در جاوااسکریپت تبدیل می‌کند.

این متد نیز یک Promise برمی‌گرداند، بنابراین برای آن هم از await استفاده می‌کنیم.

مرحله سوم: برگرداندن داده

return resData;

در نهایت، داده پردازش‌شده را از متد خارج می‌کنیم.


4. تست متد GET

در فایل app.js ابتدا یک نمونه از کلاس می‌سازیم:

const http = new EasyHTTP();

حالا درخواست دریافت کاربران را ارسال می‌کنیم:

http.get('https://jsonplaceholder.typicode.com/users')
  .then((data) => console.log(data))
  .catch((err) => console.log(err));

اگر درخواست موفق باشد، لیست کاربران در کنسول نمایش داده می‌شود.

با وجود اینکه داخل کتابخانه از async/await استفاده کرده‌ایم، خروجی متد get همچنان یک Promise است. دلیل آن این است که هر تابعی که با async تعریف شود، به‌صورت خودکار Promise برمی‌گرداند.

بنابراین در فایل app.js می‌توانیم از .then() و .catch() برای دریافت نتیجه استفاده کنیم.


چرا نمی‌توانیم این‌طور داده را دریافت کنیم؟

const users = http.get(
  'https://jsonplaceholder.typicode.com/users'
);

console.log(users);

در این حالت، مقدار users خود داده‌ها نیست؛ بلکه یک Promise است که در آینده داده‌ها را تولید می‌کند.

برای دریافت مقدار نهایی باید صبر کنیم:

http.get('https://jsonplaceholder.typicode.com/users')
  .then((users) => {
    console.log(users);
  })
  .catch((err) => {
    console.log(err);
  });

5. بازنویسی متد POST

متد post برای ارسال یک داده جدید به سرور استفاده می‌شود.

در این متد، تنظیمات درخواست همچنان مانند نسخه قبلی است:

  • method برابر POST
  • تعیین هدر Content-Type
  • تبدیل داده با JSON.stringify
class EasyHTTP {
  async get(url) {
    const response = await fetch(url);
    const resData = await response.json();

    return resData;
  }

  async post(url, data) {
    const response = await fetch(url, {
      method: 'POST',
      headers: {
        'Content-type': 'application/json'
      },
      body: JSON.stringify(data)
    });

    const resData = await response.json();

    return resData;
  }
}

نحوه عملکرد متد POST

ارسال داده

body: JSON.stringify(data)

داده‌ای که به متد post می‌دهیم معمولاً یک آبجکت جاوااسکریپتی است.
برای ارسال آن در بدنه درخواست HTTP، باید آن را به رشته JSON تبدیل کنیم از### خواندن پاسخ

const resData = await response.json();

پس از دریافت پاسخ، آن را به JSON تبدیل می‌کنیم و نتیجه را برمی‌گردانیم.


6. تست متد POST

ابتدا داده‌ای را برای کاربر جدید تعریف می‌کنیم:

const userData = {
  name: 'John Doe',
  username: 'John Doe',
  email: 'johndoe@gmail.com'
};

سپس متد post را اجرا می‌کنیم:

http.post(
  'https://jsonplaceholder.typicode.com/users',
  userData
)
  .then((data) => console.log(data))
  .catch((err) => console.log(err));

پاسخ API شبیه نمونه زیر خواهد بود:

{
  name: 'John Doe',
  username: 'John Doe',
  email: 'johndoe@gmail.com',
  id: 11
}

در API آزمایشی JSONPlaceholder، کاربران اولیه معمولاً تا شناسه 10 وجود دارند؛ بنابراین کاربر جدید در پاسخ با شناسه شبیه 11 نمایش داده می‌شود.


7. بازنویسی متد PUT

متد put برای به‌روزرسانی داده‌های یک منبع موجود استفاده می‌شود.

ساختار آن تقریباً با post یکسان است. تنها تفاوت اصلی، مقدار method است:

class EasyHTTP {
  async get(url) {
    const response = await fetch(url);
    const resData = await response.json();

    return resData;
  }

  async post(url, data) {
    const response = await fetch(url, {
      method: 'POST',
      headers: {
        'Content-type': 'application/json'
      },
      body: JSON.stringify(data)
    });

    const resData = await response.json();

    return resData;
  }

  async put(url, data) {
    const response = await fetch(url, {
      method: 'PUT',
      headers: {
        'Content-type': 'application/json'
      },
      body: JSON.stringify(data)
    });

    const resData = await response.json();

    return resData;
  }
}

در اینجا نیز:

  • داده جدید با JSON.stringify به JSON تبدیل می‌شود
  • پاسخ با response.json() خوانده می‌شود
  • داده نهایی از متد برگردانده می‌شود

8. تست متد PUT

داده جدیدی برای به‌روزرسانی کاربر تعریف می‌کنیم:

const updatedUser = {
  name: 'Jane Doe',
  username: 'Jane Doe',
  email: 'janedoe@gmail.com'
};

حالا کاربر با شناسه 2 را به‌روزرسانی می‌کنیم:

http.put(
  'https://jsonplaceholder.typicode.com/users/2',
  updatedUser
)
  .then((data) => console.log(data))
  .catch((err) => console.log(err));

در پاسخ، اطلاعات به‌روزشده کاربر با شناسه 2 نمایش داده می‌شود.


9. بازنویسی متد DELETE

درخواست DELETE برای حذف یک منبع استفاده می‌شود.

در این درخواست معمولاً نیازی به ارسال داده یا body نداریم:

class EasyHTTP {
  async get(url) {
    const response = await fetch(url);
    const resData = await response.json();

    return resData;
  }

  async post(url, data) {
    const response = await fetch(url, {
      method: 'POST',
      headers: {
        'Content-type': 'application/json'
      },
      body: JSON.stringify(data)
    });

    const resData = await response.json();

    return resData;
  }

  async put(url, data) {
    const response = await fetch(url, {
      method: 'PUT',
      headers: {
        'Content-type': 'application/json'
      },
      body: JSON.stringify(data)
    });

    const resData = await response.json();

    return resData;
  }

  async delete(url) {
    await fetch(url, {
      method: 'DELETE'
    });

    return 'Resource deleted';
  }
}

در اینجا فقط منتظر اجرای درخواست می‌مانیم و پس از موفقیت، پیام زیر را برمی‌گردانیم:

return 'Resource deleted';

چون API آزمایشی ممکن است برای درخواست حذف یک آبجکت خالی برگرداند، نیازی به اجرای response.json() نداریم.


10. تست متد DELETE

برای حذف کاربر با شناسه 2:

http.delete('https://jsonplaceholder.typicode.com/users/2')
  .then((message) => console.log(message))
  .catch((err) => console.log(err));

خروجی:

Resource deleted

11. بررسی خطاهای HTTP

یک نکته مهم درباره fetch این است که در صورت دریافت وضعیت‌هایی مانند 404 یا 500، همیشه Promise را reject نمی‌کند. بنابراین بهتر است وضعیت پاسخ را نیز بررسی کنیم.

برای این کار می‌توانیم در هر متد بعد از دریافت پاسخ، ویژگی response.ok را بررسی کنیم:

if (!response.ok) {
  throw new Error(`HTTP Error: ${response.status}`);
}

برای مثال، نسخه کامل‌تر متد get:

async get(url) {
  const response = await fetch(url);

  if (!response.ok) {
    throw new Error(`HTTP Error: ${response.status}`);
  }

  const resData = await response.json();

  return resData;
}

اگر خطایی با throw ایجاد شود، Promise تابع reject می‌شود و در فایل app.js توسط .catch() دریافت خواهد شد:

http.get('https://jsonplaceholder.typicode.com/invalid-url')
  .then((data) => console.log(data))
  .catch((err) => console.log(err.message));

12. نسخه کامل EasyHTTP با async/await

class EasyHTTP {
  async get(url) {
    const response = await fetch(url);

    if (!response.ok) {
      throw new Error(`HTTP Error: ${response.status}`);
    }

    const resData = await response.json();

    return resData;
  }

  async post(url, data) {
    const response = await fetch(url, {
      method: 'POST',
      headers: {
        'Content-type': 'application/json'
      },
      body: JSON.stringify(data)
    });

    if (!response.ok) {
      throw new Error(`HTTP Error: ${response.status}`);
    }

    const resData = await response.json();

    return resData;
  }

  async put(url, data) {
    const response = await fetch(url, {
      method: 'PUT',
      headers: {
        'Content-type': 'application/json'
      },
      body: JSON.stringify(data)
    });

    if (!response.ok) {
      throw new Error(`HTTP Error: ${response.status}`);
    }

    const resData = await response.json();

    return resData;
  }

  async delete(url) {
    const response = await fetch(url, {
      method: 'DELETE'
    });

    if (!response.ok) {
      throw new Error(`HTTP Error: ${response.status}`);
    }

    return 'Resource deleted';
  }
}

13. نمونه استفاده کامل در app.js

const http = new EasyHTTP();

const userData = {
  name: 'John Doe',
  username: 'John Doe',
  email: 'johndoe@gmail.com'
};

// GET
http.get('https://jsonplaceholder.typicode.com/users')
  .then((data) => console.log('Users:', data))
  .catch((err) => console.log(err.message));

// POST
http.post(
  'https://jsonplaceholder.typicode.com/users',
  userData
)
  .then((data) => console.log('Created user:', data))
  .catch((err) => console.log(err.message));

// PUT
http.put(
  'https://jsonplaceholder.typicode.com/users/2',
  userData
)
  .then((data) => console.log('Updated user:', data))
  .catch((err) => console.log(err.message));

// DELETE
http.delete('https://jsonplaceholder.typicode.com/users/2')
  .then((message) => console.log(message))
  .catch((err) => console.log(err.message));

برای بررسی هر درخواست به‌صورت جداگانه، بهتر است در هر بار اجرا فقط یک نمونه را فعال نگه داریم و نمونه‌های دیگر را موقتاً کامنت کنیم.


جمع‌بندی

در نسخه سوم EasyHTTP، ساختار Promiseهای دستی نسخه قبل را حذف کردیم و متدها را با async/await نوشتیم.

مهم‌ترین نکات:

  • هر تابعی که با async تعریف شود، به‌صورت خودکار Promise برمی‌گرداند.
  • await fetch(url) منتظر دریافت پاسخ HTTP می‌ماند.
  • await response.json() پاسخ را به داده قابل استفاده در جاوااسکریپت تبدیل می‌کند.
  • در متدهای POST و PUT، داده باید با JSON.stringify به JSON تبدیل شود.
  • در متد DELETE معمولاً نیازی به body یا خواندن JSON پاسخ نیست.
  • برای مدیریت خطاهای HTTP بهتر است response.ok بررسی شود.
  • خروجی متدهای async همچنان با .then() و .catch() قابل استفاده است.

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

// 0 comments
#JavaScript#EasyHTTP#AsyncAwait#Fetch API#کدنویسی مدرن#آموزش جاوااسکریپت

نظرات (0)

نظر خود را بنویسید