توسعه فرانت‌اند

Fetch API و Axios: مقایسه و بهترین شیوه‌ها

مقایسه رفتار HTTP، Timeout، لغو، Retry و امنیت با نمونه کامل Node و سرور محلی؛ تفاوت Client HTTP با کش و مدیریت Server State.

1404/11/04 2 دقیقه مطالعه 2,507 بازدید

ارسال درخواست HTTP فقط انتخاب میان Fetch و Axios نیست. باید بدانید موفقیت چه معنایی دارد، خطا چگونه به رابط می‌رسد، درخواست چه زمانی لغو می‌شود و داده پاسخ چه شکلی دارد. Fetch بخشی از پلتفرم وب است و در محیط‌های جدید Node نیز در دسترس قرار دارد؛ Axios یک کتابخانه با قرارداد و امکانات خودش است. هر دو می‌توانند برای پروژه مناسب باشند، اما رفتار پیش‌فرض یکسان ندارند.

انتخاب را از نیاز شروع کنید. برای چند درخواست ساده، وابستگی تازه شاید ضروری نباشد. برای تیمی با قراردادهای متمرکز، Instanceها و زنجیره Interceptor، یک Client کتابخانه‌ای می‌تواند مفید باشد. در هر دو حالت باید مدیریت خطا، امنیت و طول عمر درخواست را طراحی کنید. کوتاهی یک خط درخواست معیار کافی برای انتخاب نیست؛ بخش دشوار معمولاً رفتار برنامه در شرایط ناموفق است.

Fetch در برابر خطای HTTP چه می‌کند؟

دریافت پاسخ ۴۰۴ یا ۵۰۰ معمولاً Promise مربوط به Fetch را صرفاً به علت آن وضعیت Reject نمی‌کند. باید response.ok یا status را بررسی کنید و تصمیم بگیرید چه چیزی خطای برنامه است. خطای شبکه و لغو مسیر دیگری دارند. مستندات fetch در MDN این تفاوت را روشن می‌کند. قراردادن فقط catch بدون بررسی وضعیت می‌تواند پاسخ ناموفق را در مسیر موفقیت نگه دارد.

خواندن Body نیز مرحله جداست. ممکن است پاسخ موفق باشد اما JSON معتبر نباشد، یا داده شکل مورد انتظار را نداشته باشد. Content-Type و قرارداد API را بشناسید و داده را پیش از مصرف بررسی کنید. تایپ TypeScript به تنهایی محتوای شبکه را در زمان اجرا معتبر نمی‌کند. برای داده حساس یا پیچیده، اعتبارسنجی مناسب لازم است؛ راهنمای Zod به همین مرز میان تایپ و داده بیرونی می‌پردازد.

Axios چه قرارداد متفاوتی دارد؟

Axios به طور پیش‌فرض وضعیت‌های خارج از بازه موفقیت تعیین‌شده را در مسیر خطا قرار می‌دهد و داده پاسخ را در response.data ارائه می‌کند. این رفتار با تنظیم validateStatus قابل تغییر است. بنابراین «Axios همیشه برای هر خطای سرور Reject می‌کند» بدون اشاره به تنظیمات دقیق نیست. راهنمای رسمی مدیریت خطا تفاوت پاسخ دریافت‌شده، درخواست بدون پاسخ و خطای آماده‌سازی را توضیح می‌دهد.

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

نمونه کامل با سرور محلی و دو Client

نمونه زیر برای Node ۲۲ به بالا نوشته شده و به مرورگر، HTML یا سرویس بیرونی وابسته نیست. پوشه‌ای تازه بسازید، سه فایل package.json، server.mjs و client.mjs را در آن قرار دهید و npm install را اجرا کنید. سپس در ترمینال اول npm run server و در ترمینال دوم npm run client را اجرا کنید. سرور فقط روی 127.0.0.1 و پورت ۴۳۱۷ گوش می‌دهد؛ برای پایان، در ترمینال سرور Ctrl+C بزنید.

فایل package.json

{"type":"module","scripts":{"server":"node server.mjs","client":"node client.mjs"},"dependencies":{"axios":"^1.0.0"}}

فایل server.mjs

import { createServer } from 'node:http';
createServer((request, response) => {
  response.setHeader('Content-Type', 'application/json; charset=utf-8');
  if (request.url === '/items') {
    response.end(JSON.stringify([{ id: 1, title: 'اول' }, { id: 2, title: 'دوم' }]));
  } else {
    response.statusCode = 404;
    response.end(JSON.stringify({ message: 'پیدا نشد' }));
  }
}).listen(4317, '127.0.0.1', () => console.log('http://127.0.0.1:4317'));

فایل client.mjs

import axios from 'axios';
const baseURL = 'http://127.0.0.1:4317';
async function withFetch(path) {
  const controller = new AbortController();
  const timer = setTimeout(() => controller.abort(), 3000);
  try {
    const response = await fetch(baseURL + path, { signal: controller.signal });
    if (!response.ok) throw new Error(`HTTP ${response.status}`);
    return await response.json();
  } finally {
    clearTimeout(timer);
  }
}
const client = axios.create({ baseURL, timeout: 3000 });
for (const path of ['/items', '/missing']) {
  for (const [name, read] of [
    ['Fetch', () => withFetch(path)],
    ['Axios', async () => (await client.get(path)).data],
  ]) {
    try { console.log(name, path, await read()); }
    catch (error) { console.log(name, path, error.message); }
  }
}

مسیر items دو مورد ثابت آموزشی می‌دهد و هر مسیر دیگر پاسخ ۴۰۴ دارد. Client هر دو مسیر را با Fetch و Axios می‌خواند. در Fetch بررسی ok را خودمان اضافه کرده‌ایم؛ در Axios مسیر خطا طبق قرارداد پیش‌فرض کتابخانه رخ می‌دهد. داده‌ها ساختگی‌اند و هیچ احراز هویت یا عملیات نوشتن در نمونه وجود ندارد. نمونه برای توضیح قرارداد است و ادعای اجرای موفق در این مقاله مطرح نمی‌شود.

لغو و Timeout را جدا طراحی کنید

در Fetch، AbortController می‌تواند برای لغو استفاده شود. سیگنال را به درخواست بدهید و در زمان مناسب abort را فراخوانی کنید. برای Timeout می‌توانید یک زمان‌سنج بسازید و حتماً آن را پاک کنید، مانند نمونه. این سازوکار زمان انتظار کل را در سطح Client کنترل می‌کند، اما تضمین نمی‌کند سرور عملیات را متوقف کرده است. درخواست ارسال‌شده ممکن است در سمت سرور هنوز اثر داشته باشد.

Axios نیز از AbortController پشتیبانی می‌کند و timeout تنظیم جداگانه‌ای دارد. راهنمای رسمی Cancellation تفاوت‌ها و جایگزینی روش قدیمی CancelToken را توضیح می‌دهد. از نمونه‌های قدیمی بدون بررسی نسخه کپی نکنید. در رابط، لغو ناشی از تغییر صفحه با خطای واقعی یکسان نیست؛ شاید نیازی به نمایش پیام قرمز برای عملی که کاربر عمداً متوقف کرده نباشد.

پاسخ قدیمی نباید داده تازه را بازنویسی کند

در جست‌وجو، درخواست اول ممکن است دیرتر از درخواست دوم برگردد. اگر هر پاسخ مستقیماً روی وضعیت نوشته شود، نتیجه قدیمی می‌تواند نمایش جدید را جایگزین کند. لغو درخواست قبلی کمک می‌کند، اما بهتر است اعتبار نتیجه نسبت به ورودی فعلی نیز روشن باشد. شناسه درخواست یا قرارداد ابزار Server State می‌تواند این رابطه را مدیریت کند. انتخاب Fetch یا Axios به تنهایی مشکل ترتیب پاسخ‌ها را حل نمی‌کند.

برای جست‌وجوی پرتکرار، Debounce می‌تواند تعداد شروع درخواست را کم کند، اما جای مدیریت پاسخ قدیمی را نمی‌گیرد. راهنمای Debounce و Throttle تفاوت کنترل نرخ و رفتار اجرا را توضیح می‌دهد. درباره زمان مناسب از تجربه کاربر و نیاز محصول تصمیم بگیرید. تأخیر طولانی فقط برای کمترشدن عدد درخواست ممکن است رابط را کند و مبهم کند.

Retry برای همه عملیات امن نیست

تکرار خواندن ناموفق با تکرار پرداخت یا ثبت سفارش یکسان نیست. ممکن است سرور عملیات را انجام داده باشد اما پاسخ به Client نرسیده باشد. Retry بی‌قاعده می‌تواند اثر تکراری بسازد. برای عملیات نوشتن، قرارداد Idempotency و رفتار سرور باید روشن باشد. کتابخانه نمی‌تواند بدون قرارداد کسب‌وکار تصمیم درست بگیرد. تعداد تلاش، فاصله و شرایط توقف را متناسب تعریف کنید و حلقه بی‌پایان نسازید.

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

CORS و امنیت با عوض‌کردن Client حل نمی‌شوند

در مرورگر، سیاست CORS به همکاری سرور و محیط اجرا وابسته است. تغییر Fetch به Axios مجوز سرور را ایجاد نمی‌کند. حالت no-cors نیز راه خواندن آزاد پاسخ محدودشده نیست. در نمونه Node، محدودیت مرورگری CORS مطرح نشده؛ این تفاوت محیط را حفظ کنید. Cookie، Credentials و خطر CSRF باید با معماری احراز هویت بررسی شوند. هیچ گزینه عمومی برای امن‌کردن همه درخواست‌ها وجود ندارد.

Secret واقعی نباید داخل Bundle مرورگر قرار گیرد. مقدار محیطی که وارد کد کلاینت می‌شود معمولاً قابل مشاهده است. Client HTTP محل مناسب پنهان‌کردن کلید سرویس نیست. برای درخواست سمت سرور، مقصد و ورودی را هم اعتبارسنجی کنید تا کاربر نتواند سرور را به مقصد دلخواه بفرستد. نمونه محلی از نشانی ثابت استفاده می‌کند و پراکسی عمومی نمی‌سازد. مرز اعتماد را مستقل از ابزار مشخص کنید.

قرارداد تیم مهم‌تر از Wrapper بزرگ است

یک لایه کوچک می‌تواند Base URL، خطای مشترک و روش لغو را استاندارد کند. لازم نیست تمام امکانات Client را پشت Wrapperی پیچیده پنهان کنید. مصرف‌کننده باید بفهمد چه داده‌ای برمی‌گردد و چه خطایی رخ می‌دهد. برای کش، تازگی و همگام‌سازی، TanStack Query مسئله متفاوتی را حل می‌کند و می‌تواند از Fetch یا Axios در تابع دریافت داده استفاده کند.

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

واکنش به این مقاله

اشتراک‌گذاری این مقاله:
دیدگاه‌ها 0
۰ / ۲۰۰۰

هنوز دیدگاهی ثبت نشده

اولین نفری باشید که نظر می‌دهد!