Skip to content

Ganti Axios dengan Wrapper fetch Sederhana Buatan Sendiri

Published: at 00.00 (6 min read)

This is a translation of the original post Replace axios with a simple custom fetch wrapper by Kent C. Dodds.

Saya ingat bersama Matt Zabriskie ketika dia mendapatkan ide tentang versi JavaScript murni dari layanan $http AngularJS. Itu terdengar seperti ide yang cemerlang dan malam itu di kamar hotelnya di MidwestJS, dia merakit iterasi pertama.

Itu luar biasa karena bekerja dengan XMLHttpRequest mentah untuk melakukan permintaan HTTP tidaklah sangat menyenangkan. Perpustakaannya, yang kemudian dia sebut axios adalah karya brilian dan berfungsi baik di NodeJS maupun Browser yang saya ingat dia sangat bersemangat tentang hal itu (dan saya juga).

Sudah hampir enam tahun sekarang dan jika Anda membaca ini kemungkinan Anda setidaknya telah mendengar tentangnya dan sangat mungkin telah menggunakannya di masa lalu atau sedang menggunakannya sekarang. Ini memiliki jumlah unduhan yang besar dan terus bertambah di npm. Dan meskipun Matt sudah lama bergerak dari proyek tersebut, ini masih aktif dipelihara.

Sejak dirilis, standar browser telah berkembang untuk menambahkan API baru berbasis promise untuk melakukan permintaan HTTP yang menyediakan pengalaman pengembang yang jauh lebih menyenangkan. API ini disebut fetch dan jika Anda belum menggunakannya, Anda benar-benar harus memeriksanya. Ini didukung secara luas dan mudah dipolifil (favorit saya adalah unfetch karena maskot anjingnya lucu 🐶).

Berikut beberapa alasan Anda mungkin mempertimbangkan mengganti axios dengan pembungkus kustom sederhana di sekitar fetch:

Saya memiliki pembungkus fetch untuk aplikasi bookshelf saya yang telah melayani saya dengan baik. Mari kita bangun bersama:

function client(endpoint, customConfig) {
  const config = {
    method: "GET",
    ...customConfig,
  };

  return window
    .fetch(`${process.env.REACT_APP_API_URL}/${endpoint}`, config)
    .then(response => response.json());****
}

Fungsi client ini memungkinkan saya untuk melakukan panggilan ke API aplikasi saya seperti ini:

client(`books?query=${encodeURIComponent(query)}`).then(
  data => {
    console.log("ini adalah buku-bukunya", data.books);
  },
  error => {
    console.error("oh tidak, terjadi kesalahan", error);
  }
);

Namun, API window.fetch bawaan tidak menangani kesalahan dengan cara yang sama seperti axios. Secara default, window.fetch hanya akan menolak janji jika permintaan itu sendiri gagal (kesalahan jaringan), bukan jika mengembalikan “Client error response”. Untungnya, objek Response memiliki properti ok yang dapat kita gunakan untuk menolak janji dalam pembungkus kami:

function client(endpoint, customConfig = {}) {
  const config = {
    method: "GET",
    ...customConfig,
  };

  return window
    .fetch(`${process.env.REACT_APP_API_URL}/${endpoint}`, config)
    .then(async response => { 
      if (response.ok) { 
        return await response.json(); 
      } else { 
        const errorMessage = await response.text(); 
        return Promise.reject(new Error(errorMessage)); 
      } 
    }); 
}

Bagus, sekarang rantai janji (promise chain) kami akan ditolak jika responsnya tidak ok.

Hal selanjutnya yang ingin kita lakukan adalah dapat mengirim data ke backend. Kami dapat melakukan ini dengan API kami saat ini, tetapi mari kita buat lebih mudah:

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 => {
      if (response.ok) {
        return await response.json();
      } else {
        const errorMessage = await response.text();
        return Promise.reject(new Error(errorMessage));
      }
    });
}

Keren, jadi sekarang kita bisa melakukan hal-hal seperti ini:

client("login", { body: { username, password } }).then(
  data => {
    console.log("ini adalah data pengguna yang login", data);
  },
  error => {
    console.error("oh tidak, login gagal", error);
  }
);

Selanjutnya kita ingin dapat melakukan permintaan yang diautentikasi. Ada berbagai pendekatan untuk melakukan ini, tetapi inilah cara saya melakukannya di aplikasi bookshelf:

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.ok) {
        return await response.json();
      } else {
        const errorMessage = await response.text();
        return Promise.reject(new Error(errorMessage));
      }
    });
}

Jadi pada dasarnya jika kita memiliki token di localStorage dengan kunci tersebut, maka kita menambahkan header Otorisasi (sesuai spesifikasi JWT) yang kemudian dapat digunakan server kami untuk menentukan apakah pengguna diizinkan. Praktik yang sangat umum di sana.

Hal berguna lain yang dapat kita lakukan adalah jika response.status adalah 401, itu berarti token pengguna tidak valid (mungkin kadaluarsa atau sesuatu) jadi kita dapat secara otomatis logout pengguna dan menyegarkan halaman untuk mereka:

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; 
      } 
      if (response.ok) {
        return await response.json();
      } else {
        const errorMessage = await response.text();
        return Promise.reject(new Error(errorMessage));
      }
    });
}

function logout() { 
  window.localStorage.removeItem(localStorageKey); 
} 

Tergantung pada situasi Anda, mungkin Anda akan mengarahkan mereka ke layar login sebagai gantinya.

Di atas ini, aplikasi bookshelf memiliki beberapa pembungkus lain untuk membuat permintaan. Seperti list-items-client.js:

import { client } from "./api-client";

function create(listItemData) {
  return client("list-items", { body: listItemData });
}

function read() {
  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 };

Kesimpulan

Axios melakukan BANYAK untuk Anda dan jika Anda senang menggunakannya maka silakan terus gunakan (saya menggunakannya untuk proyek node karena itu hanya hebat dan saya belum termotivasi untuk menyelidiki alternatif yang saya yakin Anda sangat ingin memberi tahu saya semua tentang sekarang). Tapi untuk browser, saya pikir Anda sering akan lebih baik membuat pembungkus sederhana Anda sendiri di sekitar fetch yang melakukan persis apa yang Anda butuhkan untuk dilakukan dan tidak lebih. Apa pun yang dapat Anda pikirkan untuk interceptor atau transform axios untuk dilakukan, Anda dapat membuat pembungkus Anda melakukannya. Jika Anda tidak ingin itu diterapkan ke semua permintaan, maka Anda dapat membuat pembungkus untuk pembungkus Anda.