blog

به جای axios از fetch ساده استفاده کنید

ایده‌ی Matt Zabriskie خیلی فوق‌العاده بود. زیرا باعث می‌شد که دیگر نیازی به استفاده از XMLHttpRequest برای درخواست‌های HTTP نداشته باشیم. کتابخانه او، که بعدا با نام axios منتشر شد، یک ایده عالی بود که هم در Node.js و هم در مرورگر کار می‌کرد.

الان تقریبا شش سال از آن زمان می‌گذرد و اگر شما دارید این مقاله را می‌خوانید، احتمالا باید قبلا از آن استفاده کرده باشید یا این که حداقل اسم آن را شنیده باشید. این کتاب‌خانه تعداد بسیار زیادی دانلود در NPM دارد و این عدد روز‌به‌روز بیشتر می‌شود. و با این حال که خود Matt خیلی وقت است که دیگر روی این پروژه کار نمی‌کند، اما این پروژه بسیار آپدیت می‌شود و در دست بزرگ‌تر شدن است.

زمانی که این کتاب‌خانه منتشر شد، مرورگر‌ها خودشان در حال ساخت استاندارد promise-based جدیدی برای درخواست‌های HTTP بودند که به برنامه‌نویس‌ها تجربه خیلی فوق‌العاده‌تری می‌داد. اسم این API FETCH‌ است و اگر تا کنون از آن استفاده نکرده‌اید. باید حتما یه نگاهی به آن بیاندازید

خب در زیر می‌تواند چند دلیل برای این که بهتر است axios را با یک wrapper ساده‌ی fetch عوض کنید، ببینید:

  1. بسیار ساده‌تر برای یادگیری
  2. سایز bundle کوچک‌تر
  3. مشکلات کم‌تر زمانی که نیاز به آپدیت پکیج‌ها و ... می‌کنید
  4. رفع فوری باگ‌ها
  5. مفهموم ساده‌تر

من برای یکی از برنامه‌هایم یک fetch wrapper ساده درست کردم که در این مقاله مرحله به مرحله، سعی به ساخت دوباره این fetch wrapper می‌کنیم.

function client(endpoint, customConfig) {
  const config = {
    method: 'GET',
    ...customConfig,
  }
  return window
    .fetch(`${process.env.REACT_APP_API_URL}/${endpoint}`, config)
    .then(response => response.json())
}

فانکشن client به ما این اجازه را می‌دهد که به api برنامه‌مان درخواست بزنیم. مثلا:

client(`books?query=${encodeURIComponent(query)}`).then(
  data => {
    console.log('here are the books', data.books)
  },
  error => {
    console.error('oh no, an error happened', error)
  },
)

به هر حال، apiای که window.fetch به شما می‌دهد، به روش axios ارور‌ها را هندل نمی‌کند. در حالت عادی، window.fetch تنها زمانی promise را reject می‌کند که خود درخواست ما موفق نباشد. (network error). اما اگر بک‌اند یک client error برگرداند این اتفاق نمی‌افتد. خوشبختانه آبجکت Response یک پراپرتی ok‌ دارد که می‌توانیم از آن برای reject کردن promise استفاده کنیم.

function client(endpoint, customConfig = {}) {
  const config = {
    method: 'GET',
    ...customConfig,
  }
  return window
    .fetch(`${process.env.REACT_APP_API_URL}/${endpoint}`, config)
    .then(async response => {
      const data = await response.json()
      if (response.ok) {
        return data
      } else {
        return Promise.reject(data)
      }
    })
}

عالی شد. حالا promise ما اگر پاسخ ok‌ نباشد reject می‌شود.

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

function client(endpoint, {body, ...customConfig} = {}) {
  const headers = {'Content-Type': 'application/json'}
  const config = {
    method: body ? 'POST' : 'GET',
    ...customConfig,
    headers: {
      ...headers,
      ...customConfig.headers,
    },
  }
  if (body) {
    config.body = JSON.stringify(body)
  }
  return window
    .fetch(`${process.env.REACT_APP_API_URL}/${endpoint}`, config)
    .then(async response => {
      const data = await response.json()
      if (response.ok) {
        return data
      } else {
        return Promise.reject(data)
      }
    })
}

خب. اکنون می‌توانیم مانند مثال زیر درخواست بفرستیم.

client('login', {body: {username, password}}).then(
  data => {
    console.log('here the logged in user data', data)
  },
  error => {
    console.error('oh no, login failed', error)
  },
)

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

const localStorageKey = '__bookshelf_token__'
function client(endpoint, {body, ...customConfig} = {}) {
  const token = window.localStorage.getItem(localStorageKey)
  const headers = {'Content-Type': 'application/json'}
  if (token) {
    headers.Authorization = `Bearer ${token}`
  }
  const config = {
    method: body ? 'POST' : 'GET',
    ...customConfig,
    headers: {
      ...headers,
      ...customConfig.headers,
    },
  }
  if (body) {
    config.body = JSON.stringify(body)
  }
  return window
    .fetch(`${process.env.REACT_APP_API_URL}/${endpoint}`, config)
    .then(async response => {
      const data = await response.json()
      if (response.ok) {
        return data
      } else {
        return Promise.reject(data)
      }
    })
}

خب پس هم‌اکنون اگر ما یک token در localStorage داشته باشیم. در آن موقع یک هدر Authorization‌ نیز می فرستیم. این token می‌تواند یک jwt token باشد که تشخیص می‌دهد آیا کاربر احراز هویت شده است یا خیر. یک کار دیگه که می‌توانیم بکنیم این است که اگر response.status ما ۴۰۱ بود، این به این معناست که user's token درست نیست ( یا اکسپایر شده است یا ... ). پس در این موقعیت ما می‌توانیم کاربر را لاگ‌اوت کنیم و صفحه را رفرش کنیم.

const localStorageKey = '__bookshelf_token__'
function client(endpoint, {body, ...customConfig} = {}) {
  const token = window.localStorage.getItem(localStorageKey)
  const headers = {'content-type': 'application/json'}
  if (token) {
    headers.Authorization = `Bearer ${token}`
  }
  const config = {
    method: body ? 'POST' : 'GET',
    ...customConfig,
    headers: {
      ...headers,
      ...customConfig.headers,
    },
  }
  if (body) {
    config.body = JSON.stringify(body)
  }
  return window
    .fetch(`${process.env.REACT_APP_API_URL}/${endpoint}`, config)
    .then(async response => {
      if (response.status === 401) {
        logout()
        window.location.assign(window.location)
        return
      }
      const data = await response.json()
      if (response.ok) {
        return data
      } else {
        return Promise.reject(data)
      }
    })
}
function logout() {
  window.localStorage.removeItem(localStorageKey)
}

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

علاوه‌بر این؛ اپلیکیشن bookshelf برای انجام درخواست چند بسته‌بندی دیگر نیز دارد.مانند list-items-client.js:

import {client} from './api-client'
function create(listItemData) {
  return client('list-items', {body: listItemData})
}
function read(listItemIds = []) {
  return client('list-items')
}
function update(listItemId, updates) {
  return client(`list-items/${listItemId}`, {
    method: 'PUT',
    body: updates,
  })
}
function remove(listItemId) {
  return client(`list-items/${listItemId}`, {method: 'DELETE'})
}
export {create, read, remove, update}

نتیجه‌گیری

Axios برای شما کلی کار می‌کند و اگر در پروژه‌تان با آن مشکلی ندارید، آن را تغییر ندهید. من در پروژه‌های node‌ از axios استفاده می‌کنم به دلیل این که کنجکاو پیدا کردن مورد‌های جدید برای این مورد نیستم.

اما برای مرورگر، من فکر می‌کنم که بهترین راه این است که خودتان یک wrapper از fetch درست کنید که دقیقا کار‌هایی که شما نیاز دارید را انجام بدهد. هر چیزی که فکر می‌کنید axios دارد و شما به آن نیاز دارید، می‌توانید کمی به پیاده‌سازی آن فکر کنید و همان را برای خودتان درست کنید.

 

منبع : roocket.ir