بررسی Fetch API در Node.js

پایدار شدن Fetch API در Node.js یکی از مورد انتظارترین به‌روزرسانی‌ها در سال‌های اخیر بوده است. این ویژگی یک روش استاندارد و مدرن برای ارسال درخواست‌های HTTP در محیط مرورگر و سرور فراهم می‌کند. برای درک بهتر اهمیت این موضوع، تاریخچه درخواست‌های HTTP، نحوه پیدایش Fetch و تأثیر پایدار شدن آن بر توسعه‌دهندگان Node را در این مقاله بررسی می‌کنیم.

تکامل درخواست‌های HTTP

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

در سال ۱۹۹۸، اینترنت اکسپلورر ۵ با معرفی API جدیدی به نام XMLHttpRequest این مشکل را تغییر داد. این API در ابتدا برای دریافت داده‌های XML از طریق HTTP طراحی شده بود و نام آن نیز از همین قابلیت گرفته شده است. اما پس از مدتی، پشتیبانی از فرمت‌های دیگر مانند JSON، HTML و plaintext نیز به آن اضافه گردید.

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

معرفی Fetch API

در سال ۲۰۱۵، Fetch API به عنوان جایگزینی مدرن برای XMLHttpRequest معرفی شد و به سرعت به استاندارد اصلی برای ارسال درخواست‌های asynchronous در برنامه‌های وب تبدیل گردید. یکی از مهم‌ترین مزایای Fetch نسبت به XMLHttpRequest استفاده از promiseها است که باعث سادگی و خوانایی بیشتر کد شده و از callback hell جلوگیری می‌کند.

با وجود اینکه Fetch API مدتی است که در دنیای وب وجود دارد، اما به دلیل برخی محدودیت‌ها به طور پیش‌فرض در Node.js قرار نگرفته بود. یکی از توسعه‌دهندگان هسته Node توضیح داده است که پیاده‌سازی Fetch در مرورگر به Web Streams API و AbortController وابسته است، در حالی که این ویژگی‌ها تا همین اواخر در Node.js در دسترس نبودند. به همین دلیل، انتخاب بهترین روش برای افزودن Fetch به هسته Node چالش‌برانگیز بود.

پیش از اضافه شدن Fetch API، محبوب‌ترین روش برای ارسال درخواست‌های HTTP در Node.js استفاده از ماژول request بود. اما با پیشرفت اکوسیستم جاوااسکریپت، این ماژول منسوخ شد. یکی از دلایل اصلی این اتفاق، عدم پشتیبانی از async/await بود که در نهایت باعث شد این پروژه از بین برود.

Undici

در سال ۲۰۱۸، کتابخانه Undici معرفی شد که یک کلاینت HTTP/1.1 جدید و سریع برای Node.js بود. این کتابخانه قابلیت‌هایی مانند pipelining و pooling را ارائه می‌داد و تیم توسعه‌دهنده Node.js روی بهبود عملکرد و پایداری آن کار کردند.

Undici به عنوان پایه‌ای برای پیاده‌سازی native fetch() در Node.js عمل کرد و یک راهکار سریع و استاندارد برای ارسال درخواست‌های HTTP فراهم نمود. با ادغام Undici در هسته Node.js، توسعه‌دهندگان به یک کلاینت HTTP قوی و سریع دسترسی پیدا کردند که زمینه را برای اضافه شدن Fetch API در نسخه ۱۸ Node فراهم کرد و در نهایت در نسخه ۲۱ پایدار شد.

Fetch API در نسخه ۲۱ Node.js پایدار شد

همان‌طور که اشاره کردیم، Fetch API در نسخه ۱۸ به Node.js اضافه شد، اما تا قبل از نسخه ۲۱ همچنان در مرحله آزمایشی قرار داشت و مشکلات و باگ‌هایی داشت که باعث ناپایداری آن می‌شد. انتشار نسخه پایدار در نسخه ۲۱ Node.js یک نقطه عطف بزرگ برای توسعه‌دهندگان محسوب می‌شود، زیرا این API اکنون به طور کامل تست شده و در شرایط مختلف عملکرد قابل اعتمادی دارد.

نحوه استفاده از Fetch API در Node.js

Fetch API یک تابع سطح بالا است و نیازی به import یا require برای استفاده از آن در برنامه‌های Node.js ندارد.

در ساده‌ترین حالت، fetch یک URL را دریافت کرده و درخواست GET را به آن ارسال می‌کند. نتیجه این درخواست یک promise است که پاسخ را برمی‌گرداند. به عنوان مثال:

fetch("http://example.com/api/endpoint")
  .then((response) => {
    // Do something with response
  })
  .catch((err) => {
    // Handle error here
    console.log("Unable to fetch -", err);
  });

همچنین می‌توانیم با ارسال یک آبجکت اختیاری، گزینه‌های بیشتری مانند نوع درخواست، هدرها و سایر تنظیمات را مشخص کنیم:

fetch("http://example.com/api/endpoint", {
  method: "POST", // Specify request method
  headers: {
    // Customize request header here
    "Content-Type": "application/json",
    // . . .
  },
  body: JSON.stringify({
    foo: "bar",
    bar: "foo",
  }),
  // . . .
})
  .then((res) => res.json())
  .then((data) => {
    console.log("Response data:", data);
  })
  .catch((err) => {
    console.log("Unable to fetch -", err);
  });

در این مثال، یک درخواست POST به یک API ارسال شده و داده‌های foo و bar در body ارسال می‌شوند. همچنین، هدر درخواست تنظیم شده است تا نوع محتوا application/json باشد. سپس، پاسخ به JSON تبدیل شده و در کنسول نمایش داده می‌شود.

مزایای استفاده از Fetch API در Node.js

پیش‌فرض بودن Fetch API در Node.js مزایای زیادی برای توسعه‌دهندگان دارد، از جمله:

عدم نیاز به نصب بسته‌های اضافی

با درون‌سازی Fetch API در Node.js، دیگر نیازی به استفاده از پکیج‌هایی مانند node-fetch ،got و cross-fetch وجود ندارد. این یعنی توسعه‌دهندگان بدون نیاز به نصب ماژول‌های اضافی می‌توانند عملیات شبکه‌ای را در Node انجام دهند.

علاوه بر این، پکیج node-fetch که محبوب‌ترین گزینه برای استفاده از Fetch در Node.js بود، اخیراً به یک پکیج ESM-only تبدیل شده است که دیگر نمی‌توانیم آن را با تابع require() در Node استفاده کنیم. این موضوع باعث شده است تا استفاده از native Fetch API در محیط Node بسیار راحت‌تر و طبیعی‌تر شود.

یکپارچگی بین پلتفرم‌ها

توسعه‌دهندگانی که قبلاً از Fetch API در فرانت‌اند استفاده می‌کردند، اکنون می‌توانند از همان API در Node نیز بهره‌مند شوند. این هماهنگی باعث می‌شود که کار با درخواست‌های HTTP در محیط Node ساده‌تر و شهودی‌تر از گذشته باشد.

اجرای سریع‌تر

همان‌طور که پیش‌تر به آن اشاره کردیم ، پیاده‌سازی جدید Fetch نیز بر پایه‌ی Undici است. در نتیجه، می‌توان انتظار داشت که عملکرد API مربوط به Fetch نیز بهبود یابد.

معایب استفاده از Fetch API در Node.js

Fetch API مرورگر ذاتاً دارای برخی محدودیت‌هاست و این محدودیت‌ها به پیاده‌سازی جدید Node.js نیز منتقل خواهند شد:

عدم پشتیبانی داخلی از progress eventها

Fetch API در Node.js از progress eventها در هنگام بارگذاری یا دانلود فایل پشتیبانی نمی‌کند. توسعه‌دهندگانی که به کنترل دقیق بر فرآیند نظارت بر پیشرفت نیاز دارند، ممکن است با این محدودیت مواجه شوند و ناچار باشند از کتابخانه‌ها یا راه‌حل‌های جایگزین استفاده کنند.

پشتیبانی محدود از نسخه‌های قدیمی‌تر Node.js

در حالی که Fetch API در نسخه‌های جدیدتر Node.js پایدار است، پروژه‌هایی که از نسخه‌های قدیمی‌تر استفاده می‌کنند ممکن است با مشکلات سازگاری روبه‌رو شوند. توسعه‌دهندگانی که در محیط‌هایی کار می‌کنند که امکان به‌روزرسانی Node.js در آن‌ها وجود ندارد، احتمالاً مجبور خواهند شد از کتابخانه‌های جایگزین استفاده کنند یا راه‌حل‌های دیگری بیابند.

پیچیدگی در مدیریت کوکی‌ها

رویکرد Fetch API در مدیریت کوکی‌ها بر اساس رفتار مرورگر است، که ممکن است در محیط Node.js نتایج غیرمنتظره‌ای ایجاد کند. هنگام کار با کوکی‌ها، توسعه‌دهندگان باید دقت لازم را داشته باشند و تنظیمات را متناسب با موارد استفاده‌ خاص پیکربندی کنند.

بررسی انتقال به Fetch رسمی و پایدار

ارتقا به نسخه‌ی رسمی و پایدار Fetch در Node.js فرآیندی نسبتاً ساده دارد که در ادامه آن را بررسی می‌کنیم:

به‌روزرسانی نسخه‌ی Node.js

ابتدا باید مطمئن شویم که نسخه Node.js ما حداقل نسخه ۱۲ یا بالاتر باشد. برای بررسی نسخه فعلی، می‌توانیم از دستور زیر استفاده کنیم:

node -v

اگر نسخه Node.js ما پایین‌تر از ۲۱ است، باید آن را به جدیدترین نسخه ارتقا دهیم. می‌توانیم آخرین نسخه را مستقیماً از وب‌سایت رسمی Node.js دانلود کنیم یا از یک Node Version Manager مانند nvm استفاده کنیم. به عنوان مثال، برای مشاهده تمامی نسخه‌های در دسترس Node.js می‌توانیم از دستور زیر استفاده کنیم:

nvm ls-remote

پس از مشاهده نسخه مورد نظر، می‌توانیم آن را با دستور زیر نصب نماییم:

nvm install x.y.z
# E.g to install v22.13.0:
nvm install v22.13.0

حذف وابستگی به کتابخانه‌های خارجی

اگر پیش از این از یک کتابخانه خارجی برای ارسال درخواست در Node.js استفاده می‌کردیم (مانند node-fetch یا یک کتابخانه HTTP سفارشی دیگر)، حالا که Fetch API به طور native در دسترس است، می‌توانیم این وابستگی‌ها را حذف کنیم:

npm uninstall node-fetch

به‌روزرسانی کد برای استفاده از Fetch native

کدهایی که در حال حاضر از یک کتابخانه خارجی استفاده می‌کنند را با پیاده‌سازی native fetch جایگزین می‌کنیم. حتماً باید نحوه نگارش کد را به‌روزرسانی کرده و مدیریت صحیح Promiseها را در نظر بگیریم.

قبل از به‌روزرسانی:

const fetch = require("node-fetch");

fetch("https://api.example.com/data")
  .then((response) => response.json())
  .then((data) => console.log(data))
  .catch((error) => console.error("Error:", error));

بعد از به‌روزرسانی بهFetch native:

fetch("https://api.example.com/data")
  .then((response) => response.json())
  .then((data) => console.log(data))
  .catch((error) => console.error("Error:", error));

با این تغییرات، کد ما ساده‌تر شده و دیگر نیازی به استفاده از وابستگی‌های خارجی نخواهد داشت.

تنظیم گزینه‌ها و هدرها

بر اساس نیازهایی که داریم، می‌توانیم گزینه‌ها و هدرهای درخواست‌های خود را بررسی و تنظیم کنیم. native Fetch API ممکن است در رفتار خود تفاوت‌هایی داشته باشد یا قابلیت‌های اضافی ارائه دهد، بنابراین بهتر است برای هرگونه نیاز خاص، مستندات رسمی را مطالعه کنیم.

تست و اشکال‌زدایی

پس از اعمال این تغییرات، باید برنامه‌ی خود را به دقت تست کنیم تا مطمئن شویم که انتقال آن به native Fetch API باعث ایجاد مشکل نشده است. به ویژه به مدیریت خطاها، timeoutها و هر منطق سفارشی مرتبط با درخواست‌های HTTP باید توجه داشته باشیم.

خطاهای رایج هنگام استفاده از Fetch در Node.js

بسیاری از توسعه‌دهندگان هنگام استفاده از Fetch در Node.js با خطاهایی مواجه می‌شوند. در ادمه قصد داریم تا برخی از رایج‌ترین مشکلات و راه‌حل‌های آن‌ها را بررسی کنیم.

عدم وجود Fetch در نسخه‌های قدیمی  Node.js

یکی از خطاهای متداول، ReferenceError: fetch is not defined است که معمولاً در نسخه‌های قدیمی‌تر از نسخه ۱۸ رخ می‌دهد. Fetch در نسخه ۱۸ به عنوان یک ویژگی آزمایشی معرفی شد و در نسخه ۲۱ به یک قابلیت پایدار تبدیل گردید. برای حل این مشکل، باید به نسخه ۱۸ یا بالاتر به‌روزرسانی کنیم.

همچنین، اگر از نسخه‌های آزمایشی (نسخه ۱۸ تا نسخه ۲۰) استفاده می‌کنیم، باید برنامه‌ی خود را با flag آزمایشی اجرا نماییم:

node --experimental-fetch example.js

علاوه بر این، برای پروژه‌های قدیمی که امکان ارتقاء نسخه Node.js را ندارند، می‌توانیم از کتابخانه‌هایی مانند node-fetch برای پشتیبانی از Fetch استفاده کنیم.

مشکلات شبکه و CORS در Fetch

خطای TypeError: Failed to fetch معمولاً به دلیل مشکلات اتصال به شبکه، invalid endpointها یا محدودیت‌های CORS رخ می‌دهد؛ یعنی زمانی که سروری که درخواست به آن ارسال می‌شود، هدرهای لازم برای مجاز دانستن درخواست از مبدا کلاینت را شامل نمی‌شود.

کارهایی که برای حل این مشکل باید انجام دهیم عبارتند از:

  • از معتبر بودن URL مقصد اطمینان حاصل نماییم.
  • بررسی کنیم که سرور برای پشتیبانی از CORS به درستی تنظیم شده باشد.
  • در صورت نیاز، از راهکارهایی مانند proxy middleware استفاده کنیم.

مشکل JSON نامعتبر در پاسخ Fetch

یکی دیگر از خطاهای رایج، TypeError: Cannot read property 'json' of undefined است که معمولاً زمانی رخ می‌دهد که قصد داریم یک پاسخ JSON را parse کنیم اما سرور داده‌ها را به فرمت مورد انتظار ارسال نکرده است.

برای حل این مشکل باید:

  • بررسی کنیم که endpoint واقعاً یک JSON معتبر return می‌کند یا خیر.
  • در برخی موارد، سرور ممکن است داده‌ها را به صورت blob، bytes یا text ارسال کند. در این صورت، از متدهای مناسب برای خواندن داده‌ها استفاده کنیم. به عنوان مثال:
fetch('https://api.example.com/data')
  .then(response => {
    if (!response.ok) {
      throw new Error(`Request failed with status: ${response.status}`);
    }
    return response.json(); // Use .blob(), .bytes(), or .text() if required
  })
  .then(data => console.log(data))
  .catch(error => console.error('Fetch error:', error));

مدیریت کوکی‌ها

Fetch در Node.js رفتاری شبیه به مرورگر دارد، اما برخلاف مرورگرها، به صورت خودکار کوکی‌ها را مدیریت نمی‌کند؛ یعنی کوکی‌ها به طور پیش‌فرض ذخیره یا ارسال نمی‌شوند، مگر اینکه آن‌ها را به صورت دستی مدیریت کنیم.

اگر یک API برای احراز هویت به کوکی‌ها وابسته باشد، باید آن‌ها را به طور صریح در درخواست‌های خود بگنجانیم یا از کتابخانه‌هایی مانند fetch-cookie یا tough-cookie برای مدیریت کوکی‌ها استفاده نماییم.

Timeout درخواست‌ها

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

const controller = new AbortController();
const timeout = setTimeout(() => controller.abort(), 5000); // Timeout after 5 seconds

fetch("http://example.com/api", { signal: controller.signal })
  .then((response) => response.json())
  .catch((err) => console.log("Fetch error:", err));

به این ترتیب، ما کنترل کاملی بر زمان انتظار برای پاسخ خواهیم داشت و می‌توانیم در صورت لزوم درخواست را لغو نماییم.

جمع‌بندی

پایدار شدن Fetch API جدید در Node.js، که با تلاش‌های بی‌وقفه تیم اصلی Node.js و نقش کلیدی کتابخانه‌ی HTTP پرکاربرد Undici ممکن شده است، گامی بزرگ برای توسعه‌دهندگان محسوب می‌شود.

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

دیدگاه‌ها:

افزودن دیدگاه جدید