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

مفاهیم پایه‌ای Fetch API

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

ساده‌ترین راه برای فراخوانی Fetch API این است که فقط یک URL به تابع fetchارسال کنیم.

fetch("https://jsonplaceholder.typicode.com/users")

این تابع یک promise حاوی داده‌های response را برمی‌گرداند. این داده‌های response حاوی ویژگی‌هایی برای وضعیت پاسخ و همچنین روش‌هایی برای تبدیل داده‌های response خام به JSON، متن یا فرمت‌های دیگر است.

fetch("https://jsonplaceholder.typicode.com/users")
  .then(res => {
    console.log(res.ok) // true
    console.log(res.status) // 200
    return res.json()
  })

کد return res.json()در مثال بالا متد jsonرا در پاسخ ما فراخوانی می‌کند و آن را از تابع .thenبرمی‌گرداند. این کار به این دلیل است که متد json نیز promiseای را برمی‌گرداند که داده‌های JSON را از پاسخ ما ارزیابی می‌کند. می‌توانیم .then دوم را زنجیره‌ای کنیم تا داده‌ها را از متد json دریافت کنیم.

fetch("https://jsonplaceholder.typicode.com/users")
  .then(res => res.json())
  .then(data => console.log(data))
  // [{ userOne }, { userTwo }, ...]

اگر داده‌ها را از یک JSON api واکشی کنیم، بیشتر درخواست‌های fetch ما به این شکل خواهند بود. ابتدا URL را fetch می‌کنیم، سپس پاسخ را به JSON تبدیل می‌کنیم و در نهایت از داده‌ها در .then نهایی استفاده می‌کنیم.

ویژگی‌های مربوط به Fetch

در این بخش درمورد برخی از کاربردهای Fetch صحبت خواهیم کرد اما اغلب اوقات برای پیکربندی آن باید گزینه‌های بیشتری را برای Fetch ارسال کنیم. تابع fetchیک پارامتر دومی که آبجکت هست را می‌گیرد که شامل لیست بزرگی از گزینه‌ها می‌باشد.

گزینه‌های رایج

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

method

تا کنون رایج‌ترین گزینه مورد استفاده، methodاست. این گزینه به ما اجازه می‌دهد تا تعیین کنیم که کدام فعل HTTP را می‌خواهیم استفاده کنیم (GET، POST، PUT، DELETE و غیره).

fetch("https://jsonplaceholder.typicode.com/users/2", {
  method: "DELETE"
})

body

اگر متد را تغییر دهیم، به احتمال زیاد باید داده‌ها را همراه با درخواست خود ارسال کنیم. اینجاست که گزینه bodyوارد عمل می‌شود. body آبجکت نمی‌پذیرد، بنابراین اگر می‌خواهیم JSON را به API منتقل کنیم، ابتدا باید آن را به یک رشته تبدیل کنیم.

fetch("https://jsonplaceholder.typicode.com/users", {
  method: "POST",
  body: JSON.stringify({ name: "Kyle" })
})

headers

اکنون انجام کارهای بالا ممکن است مانند همه کارهایی باشد که باید انجام دهیم تا JSON را به یک API منتقل کنیم، اما در واقع اینطور نیست و تابع به درستی کار نخواهد کرد. دلیل آن این است که ما باید headerهای مناسبی را تنظیم کنیم تا به API بگوییم که در حال ارسال اطلاعات JSON هستیم. گزینه headerاین امکان را به ما می‌دهد تا هر هدر HTTP را که می‌خواهیم تنظیم کنیم.

fetch("https://jsonplaceholder.typicode.com/users", {
  method: "POST",
  body: JSON.stringify({ name: "Kyle" }),
  headers: { "Content-Type": "application/json" }
})

قطعه کد بالا همان کاری است که باید انجام دهیم تا JSON را به یک API منتقل کنیم.

گزینه‌های پیشرفته‌تر

سه گزینه بالا همان چیزی است که ما برای ۹۰ درصد نیازهای خود به fetch کردن داده‌ها از آن‌ها استفاده خواهیم کرد، اما چند گزینه پیشرفته نیز وجود دارد که باید آن‌ها را بدانیم.

mode

گزینه modeبه ما این امکان را می‌دهد تا تعیین کنیم که درخواست باید یک درخواست cors، no-corsیا same-originباشد. به‌طور پیش‌فرض، همه درخواست‌های fetch به‌عنوان درخواست‌های corsتنظیم می‌شوند تا بتوانیم به منابع دیگر مبدا دسترسی داشته باشیم، اما اگر بخواهیم می‌توانیم fetch را مجبور کنیم که فقط درخواست‌های same-origin را مجاز کند. در این صورت اگر بخواهیم URLای که در همان مبدا نیست را fetch کنیم، با خطا مواجه می‌شویم.

fetch("https://jsonplaceholder.typicode.com/users", { 
  mode: "same-origin"
}).catch(e => console.error(e))

credentials

گزینه دیگری که با corsسروکار دارد، credentialsاست. این گزینه می‌تواند مقادیر omit، same-originو یا includeرا داشته باشد و تعیین می‌کند که آیا Fetch API کوکی‌ها و سایر اطلاعات مبتنی بر اعتبار را دریافت می‌کند یا خیر. omit هیچ credentialsای ارسال یا دریافت نمی‌کند. same-origin فقط credentialsها را از همان URL ارسال یا دریافت می‌کند و include همه credentialsها را از هر URLای ارسال یا دریافت می‌کند.

این گزینه به‌طور پیش‌فرض روی same-origin تنظیم شده است.

fetch("https://jsonplaceholder.typicode.com/users", { 
  credentials: "include"
})

signal

آخرین گزینه پیشرفته‌ای که باید با آن آشنا شویم گزینه signalاست. این گزینه یک AbortSignalرا می‌گیرد که می‌تواند برای لغو درخواست fetch کردن داده‌ها استفاده شود.

const controller = new AbortController()

fetch("https://jsonplaceholder.typicode.com/users", { 
  signal: controller.signal
}).catch(e => console.error(e.name)) // AbortError

controller.abort()

همانطور که در کد بالا می‌بینیم این مورد کمی پیچیده‌تر از گزینه‌های دیگر است. ابتدا باید یک AbortControllerجدید ایجاد کنیم. این کنترلر دارای یک ویژگی signalاست، همان چیزی که به گزینه signal منتقل می‌کنیم. کنترلر همچنین دارای یک متد abortمی‌باشد که در صورت فراخوانی، درخواست fetch با signalمربوط به آن لغو می‌شود. این کار باعث می‌شود که پرامیس fetch با یک استثنا AborErrorرد شود.

کاربردهای پیشرفته‌تر Fetch

یکی از مواردی که در fetch API ممکن است گیج‌کننده باشد این است که اگر پاسخ HTTP 400، ۵۰۰ یا هر خطای دیگری را دریافت کنیم، خطایی برای ما ایجاد نمی‌شود. تنها راهی که می‌توانیم تشخیص دهیم که آیا یک درخواست شکست خورده است یا خیر بررسی ویژگی okپاسخ است.

fetch("https://jsonplaceholder.typicode.com/users/-1")
  .then(res => {
    console.log(res.ok) // false
    console.log(res.status) // 404
  })

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

fetch("https://jsonplaceholder.typicode.com/users/-1")
  .then(res => {
    if (res.ok) return res.json()
    return Promise.reject(res)
  })
  .then(data => console.log(data))
  .catch(res => console.error(res.status)) // 404

اگر پاسخ بدون مشکل باشد، تمام کد را به صورت عادی نگه می‌داریم، در غیر این صورت یک promise رد شده را که حاوی پاسخ است برمی‌گردانیم تا بتوانیم آن را در یک .catchمدیریت کنیم.

همچنین گاهی اوقات می‌توانیم یک گام دیگر فراتر برویم و تابع fetch سفارشی خود را ایجاد کنیم.

function jsonFetch(url, { body, headers, ...options } = {}) {
  return fetch(url, {
    headers: { "Content-Type": "application/json", ...headers }
    body: JSON.stringify(body)
    ...options
  })
  .then(res => {
    if (res.ok) return res.json()
    return Promise.reject(res)
  })
  .then(res => res.json())
}

این تابع سفارشی از تمام کدهای اضافی که برای ارسال داده‌های JSON نیاز داریم مراقبت می‌کند و همچنان به ما این امکان را می‌دهد تا از همه گزینه‌های سفارشی fetch استفاده کنیم. همچنین throwing errorها را برای مواردی مانند ۴۰۴ کنترل می‌کند.

حال اگر احساس می‌کنیم که انجام چنین کاری بسیار دردسرساز است، می‌توانیم از کتابخانه‌ای مانند axios استفاده کنیم که کار کردن با API را به شدت ساده می‌کند.

جمع‌بندی

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