使用 axios 进行 API 请求以保持 DRY

Avatar of David Atanda
David Atanda

DigitalOcean 为您的旅程各个阶段提供云产品。 立即开始使用 $200 免费信用额度!

HTTP 请求是任何与后端服务器通信的 Web 应用程序的重要组成部分。 前端需要一些数据,因此它通过网络 HTTP 请求(或 Ajax,因为它通常被称为)请求数据,服务器返回一个答案。 如今,几乎每个网站都以某种方式执行此操作。

对于一个较大的网站,我们可以预期会看到更多这样的操作。 更多数据、更多 API 和更多特殊情况。 当网站像这样增长时,保持组织性很重要。 一个经典的概念是 DRY(Don’t Repeat Yourself 的缩写),它指的是将代码抽象化以防止重复使用的过程。 这是理想的,因为它通常允许我们只编写一次代码,在多个地方使用它,并在一个地方更新,而不是在每个实例中更新。

我们可能还会使用库来帮助我们。 对于 Ajax,axios 是一个流行的选择。 您可能已经熟悉它,甚至在开发过程中使用它来执行独立的 POST 和 GET 请求。

安装和基本知识

可以使用 npm(或 yarn)安装它

npm install axios

使用 Axios 的独立 POST 请求如下所示

axios.post('https://axios-app.firebaseio.com/users.json', formData)
  .then(res => console.log(res))
  .catch(error => console.log(error))

原生 JavaScript 也有多种方法来执行 JavaScript。 值得注意的是,fetch()。 那么为什么要使用库呢? 首先,fetch 中的错误处理非常奇怪。 使用 axios,您将从一开始就拥有更好的体验。 如果您想看到比较,我们有一篇文章涵盖了这两种方法,还有一篇文章讨论了这种抽象的价值

选择 axios 的另一个原因是什么? 它为我们提供了更多保持 DRY 的机会,让我们深入了解一下。

全局配置

我们可以设置一个全局配置(例如在我们的 main.js 文件中),使用标准配置来处理所有应用程序请求,该配置通过 axios 附带的默认对象设置。

此对象包含

  • baseURL: 一个相对 URL,充当所有请求的前缀,每个请求都可以附加 URL
  • headers: 可以根据请求设置的自定义标头
  • timeout: 请求中止的点,通常以毫秒为单位测量。 默认值为 0,表示不适用。
  • withCredentials: 指示是否应使用凭据进行跨站点访问控制请求。 默认值为 false
  • responseType: 指示服务器将返回的数据类型,选项包括 json(默认)、arraybuffer、documenttextstream
  • responseEncoding: 指示用于解码响应的编码。 默认值为 utf8
  • xsrfCookieName: 用作 XSRF 令牌值的 cookie 的名称,默认值为 XSRF-TOKEN
  • xsrfHeaderName: 传递 XSRF 令牌值的 HTTP 标头的名称。 默认值为 X-XSRF-TOKEN
  • maxContentLength: 定义允许的 HTTP 响应内容最大大小(以字节为单位)
  • maxBodyLength: 定义允许的 HTTP 请求内容最大大小(以字节为单位)

大多数情况下,您只会使用 baseURLheader,以及可能 timeout。 其余的不太常用,因为它们具有智能默认值,但知道它们的存在以备不时之需还是不错的。

这就是 DRY 的作用。 对于每个请求,我们不必重复 API 的 baseURL,也不必重复我们可能需要在每个请求中使用的重要标头。

以下是一个示例,其中我们的 API 具有一个基础,但它还具有多个不同的端点。 首先,我们设置一些默认值

// main.js
import axios from 'axios';


axios.defaults.baseURL = 'https://axios-app.firebaseio.com' // the prefix of the URL
axios.defaults.headers.get['Accept'] = 'application/json'   // default header for all get request
axios.defaults.headers.post['Accept'] = 'application/json'  // default header for all POST request


Then, in a component, we can use axios more succinctly, not needing to set those headers, but still having an opportunity to customize the final URL endpoint:


// form.js component
import axios from 'axios';


export default {
  methods : {
    onSubmit () {
      // The URL is now https://axios-app.firebaseio.com/users.json
      axios.post('/users.json', formData)
        .then(res => console.log(res))
        .catch(error => console.log(error))
    }
  }
}

注意: 此示例在 Vue 中,但此概念可扩展到任何 JavaScript 环境。

自定义实例

设置“自定义实例”类似于全局配置,但作用域限定为指定组件。 因此,它仍然是一种 DRY 技术,但具有层次结构。

我们将在一个新文件中设置自定义实例(我们称之为 authAxios.js),并将它导入到“关注”组件中。

// authAxios.js
import axios from 'axios'


const customInstance = axios.create ({
  baseURL : 'https://axios-app.firebaseio.com'
})
customInstance.defaults.headers.post['Accept'] = 'application/json'


// Or like this...
const customInstance = axios.create ({
  baseURL : 'https://axios-app.firebaseio.com',
  headers: {'Accept': 'application/json'}
})

然后,我们将此文件导入到表单组件中

// form.js component


// import from our custom instance
import axios from './authAxios'


export default {
  methods : {
    onSubmit () {
      axios.post('/users.json', formData)
      .then(res => console.log(res))
      .catch(error => console.log(error))
    }
  }
}

拦截器

拦截器适用于全局配置或自定义实例可能过于通用的情况,因为如果您在其对象中设置了一个标头,它将应用于受影响组件中每个请求的标头。 拦截器能够动态更改任何对象属性。 例如,我们可以根据我们选择在拦截器中使用的任何条件发送不同的标头(即使我们在对象中已经设置了一个)。

拦截器可以位于 main.js 文件或自定义实例文件中。 请求在发送出去后会被拦截,并允许我们更改响应的处理方式。

// Add a request interceptor
axios.interceptors.request.use(function (config) {
  // Do something before request is sent, like we're inserting a timeout for only requests with a particular baseURL
  if (config.baseURL === 'https://axios-app.firebaseio.com/users.json') { 
    config.timeout = 4000 
  } else { 
    return config
  }
  console.log (config)
    return config;
  }, function (error) {
  // Do something with request error
  return Promise.reject(error);
});
 
// Add a response interceptor
axios.interceptors.response.use(function (response) {
  // Do something with response data like console.log, change header, or as we did here just added a conditional behaviour, to change the route or pop up an alert box, based on the reponse status  
  if (response.status === 200 || response.status 201) {
    router.replace('homepage') }
  else {
    alert('Unusual behaviour')
  }
  console.log(response)
  return response;
}, function (error) {
  // Do something with response error
  return Promise.reject(error);
});

拦截器顾名思义,会拦截请求和响应,并根据提供的任何条件采取不同的行动。 例如,在上面的请求拦截器中,我们仅当请求具有特定的 baseURL 时才插入了一个条件超时。 对于响应,我们可以拦截它并修改我们收到的内容,例如更改路由或显示一个警报框,具体取决于状态码。 我们甚至可以根据不同的错误代码提供多个条件。

当您的项目变得越来越大,并且您开始拥有大量路由和嵌套路由,它们都根据不同的触发器与服务器通信时,拦截器将非常有用。 除了我在上面设置的条件之外,还有许多其他情况需要使用拦截器,具体取决于您的项目。

有趣的是,我们可以弹出拦截器以防止它产生任何影响。 我们需要将拦截器分配给一个变量,并使用恰如其分地命名的 eject 方法弹出它。

const reqInterceptor = axios.interceptors.request.use(function (config) {
  // Do something before request is sent, like we're inserting a timeout for only requests with a particular baseURL
  if (config.baseURL === 'https://axios-app.firebaseio.com/users.json') {
    config.timeout = 4000
  } else {
    return config
  }
  console.log (config)
  return config;
}, function (error) {    
  // Do something with request error
  return Promise.reject(error);
});
 
// Add a response interceptor
const resInterceptor = axios.interceptors.response.use(function (response) {
  // Do something with response data like console.log, change header, or as we did here just added a conditional behaviour, to change the route or pop up an alert box, based on the reponse status  
  if (response.status === 200 || response.status 201) {
    router.replace('homepage')
  } else {
    alert('Unusual behaviour')
  }
  console.log(response)
  return response;
}, function (error) {
  // Do something with response error
  return Promise.reject(error);
});


axios.interceptors.request.eject(reqInterceptor);
axios.interceptors.request.eject(resInterceptor);

虽然不太常用,但可以将拦截器放到条件语句中,或者根据某些事件删除一个拦截器。


希望这能让你很好地了解 axios 的工作原理,以及它如何在应用程序中用于保持 API 请求 DRY。 虽然我们只是简单地介绍了一些常见用例和配置,但 axios 还有很多其他优势,您可以在文档中探索,包括取消请求和防止跨站点请求伪造等功能,以及其他令人惊叹的功能。