前端网络基础 - axios使用

本文详细探讨了XMLHttpRequest的缺点,并介绍了流行的AJAX库axios。通过对比XMLHttpRequest的繁琐步骤,展示了axios如何简化AJAX请求,包括axios的基本用法、默认配置、实例创建、请求配置、响应处理和拦截器机制。此外,还讨论了axios如何实现请求取消,展示了其在处理异步请求和优化代码结构方面的优势。
摘要由CSDN通过智能技术生成

目录

XMLHttpRequest的缺点

axios初体验

axios发送AJAX的API

axios

axios.METHOD

axios默认配置

axios.defaults.baseURL

axios.defaults.headers

axios实例

axios请求配置

axios正常响应结果

axios异常响应结果

axios拦截器

axios.interceptor.request和axios.interceptor.response

InterceptorManager.prototype.use

拦截器的执行顺序

axios取消请求


XMLHttpRequest的缺点

前面我们介绍了使用XMLHttpRequest实例来发送AJAX请求,它的具体实现步骤如下:

// 创建xhr实例
const xhr = new XMLHttpRequest() 

// 设置请求方式,请求URL
xhr.open('post', 'http://localhost:3000/test')

// 设置请求头
xhr.setRequestHeader('Content-Type', 'application/json')

// 设置返回对象xhr.response的数据格式
xhr.responseType = 'json'

// 设置请求体,并发送请求
xhr.send(JSON.stringify({
    name: 'qfc',
    age: 18
}))

// 接收异步的响应
xhr.onreadystatechange = function(){
    // 当readyState为4时,表示收到完整的响应体
    if(xhr.readyState === 4) {
        // 如果响应状态码在200~299之间,说明服务器响应成功
        if(xhr.status >= 200 && xhr.status < 300) {
            // 响应状态码,状态描述
            console.log(xhr.status)
            console.log(xhr.statusText)
            // 响应头
            console.log(xhr.getAllResponseHeaders())
            // 响应体
            console.log(xhr.response)
        }
    }
}

通过以上代码,我们可以总结出XMLHttpRequest实现AJAX请求的几个缺点:

1、步骤过多,过于繁琐

2、AJAX请求响应都与xhr实例关联,严重耦合

3、事件回调的方式获得异步响应,容易产生异步回调地狱

由于XMLHttpRequest具有的以上缺点,所以我们很少直接使用XMLHttpRequest来实现AJAX,通常情况下,我们都使用基于XMLHttpRequest实现AJAX的封装库。目前较为流行的是axios库。

axios初体验

我们使用axios来重写下上面代码的逻辑

axios({
    url: 'http://localhost:3000/test', // 设置请求URL
    method: 'post', // 设置请求方式
    headers: { // 设置请求头
        'Content-Type': 'application/json'
    },
    data: { // 设置请求体
        name: 'qfc',
        age: 18
    }
}).then(response => {
    console.log(response.status) // 响应状态码
    console.log(response.statusText) // 响应状态描述
    console.log(response.headers) // 响应头
    console.log(response.data) // 响应体
})

对比XMLHttpRequest实现的AJAX而言,axios实现AJAX的写法更加简单,结构更加清晰,同时通过Promise返回值来获取异步的响应,避免了回调地狱。

axios底层也是基于XMLHttpRequest实现的AJAX,但是axios做了较为深入的封装,避免了使用者接触XMLHttpRequest实例。

下面我们来介绍下axios的具体使用,主要分为:

  • axios发送AJAX的API
  • axios默认配置
  • axios实例
  • axios请求配置
  • axios正常响应结果
  • axios异常响应结果
  • axios拦截器
  • axios取消请求

axios发送AJAX的API

axios

// axios(config)
axios({
    url: 'http://localhost:3000/test',
    method: 'post',
    headers: {
        'Content-Type': 'application/json'
    },
    data: {
        name: 'qfc',
        age: 18
    }
})


// axios(url, config)
axios('http://localhost:3000/test', {
    method: 'post',
    headers: {
        'Content-Type': 'application/json'
    },
    data: {
        name: 'qfc',
        age: 18
    }
})

axios.METHOD

// axios.get, axios.head, axios.options, axios.delete
// (url, config)
axios.get('http://localhost:3000/test', {
    params: {
        name: 'qfc',
        age: 18
    }
})


// axios.post, axios.put, axios.patch
// (url, data, config)
axios.post('http://localhost:3000/test', {
    name: 'qfc',
    age: 18
})

可以发现,axios本身可以作为函数使用,调用axios函数可以发送AJAX请求。

axios函数入参需要传入一个config配置对象,通过config配置对象设置请求URL,请求METHOD,请求HEADER,请求BODY。

但是axios函数的config入参还可以进一步简化,比如可以将请求URL提取出来,作为axios函数的入参,从而简化config。

另外config中的method也可以提取,有了jQuery金玉在前的$.get,$.post设计,我们也可以将这些请求METHOD升级为方法。

所以axios又可以被当成对象使用,调用axios.get,axios.post等发送AJAX请求,这样就又进一步简化了config。

而axios.post,axios.put,axios.patch这些方法发送AJAX请求时,一般都需要携带请求BODY,所以可以进一步将config中的data提取作为axios.post等的入参,从而再次简化config。

通过将config中的url, method, data等关键要素提取出来,其实config也没有什么特别需要使用者传入的配置了,也就是说可以直接将config入参省略。

axios默认配置

前面我们都直接使用了axios函数,或者axios.post,axios.get这样的方式来发送AJAX请求,那么axios函数对象是如何来的呢?

其实,当我们引入了axios库后,axios库会对外保留一个默认的axios函数对象,该默认的axios函数对象会自动挂载到全局对象window下,所以我们可以直接使用。

在前面使用axios发送AJAX的例子中,我们对比原生XMLHttpRequest发送AJAX来看,axios已经做到了很牛皮的步骤优化,结构优化,那么你满意了吗?

答案是还是不够完美。

axios.defaults.baseURL

我们思考一个场景,在一个网页中,有多个对接后端接口的AJAX,这些后端接口的URL可能如下:

http://localhost:3000/order/query

http://localhost:3000/order/create

http://localhost:3000/order/delete

http://localhost:3000/order/update

那么如果我们通过axios来实现AJAX,则多个axios的url参数会出现严重的冗余部分http://localhost:3000/order

有没有办法将http://localhost:3000/order提取为公共URL前缀呢?

这就需要使用到axios的默认配置了,axios作为一个(函数)对象,它身上有一个属性defaults,我们可以在axios发送AJAX之前,基于axios.defaults设置一些默认配置

axios.defaults.baseURL = 'http://localhost:3000/order'

axios.get('/query', {
    params: {
        id: '123456'
    }
})

axios.post('/create', {
    productId: 'p123',
    price: 10,
    amount: 1
})

axios.get('/delete', {
    params: {
        id: '123456'
    }
})

axios.post('/update', {
    productId: 'p123',
    amount: 10
})

axios.defaults.baseURL的作用是,如果axios发送的AJAX请求的URL不是完整路径的话,就拼接baseURL前缀。

axios.defaults.headers

当然,除了axios.defaults.baseURL默认配置外,还有其他的如url,method,headers等,比如headers默认配置,就有很多稀奇古怪的用法,我们可以设置公共head,或者特定请求方式的head

axios.defaults.headers.common = { // 针对所有axios发送的AJAX都添加的请求头
    test: 'testCommon'
}

axios.defaults.headers.post = { // 只针对axios post AJAX添加的请求头
    try: 'tryPost'
}

axios({
    url: 'http://localhost:3000/test',
    method: 'post',
    data: {
        name: 'qfc',
        age: 18
    }
})

axios({
  url: 'http://localhost:3000/test',
  method: 'get',
  params: {
    name: 'qfc',
    age: 18
  }
})

axios实例

有了axios.defaults默认配置,axios近乎完美了,但是还是有一点欠缺。比如一个网页中,有多个对接后端接口的AJAX需求,但是后端接口的URL如下:

http://localhost:3000/order/query

http://localhost:3000/order/create

http://localhost:3000/user/query

http://locahost:3000/user/create

axios.defaults只用设置一个baseURL,因为axios.defaults只对默认的axios函数有用,而默认的axios函数只有一个。

那么此时该如何搞呢?

axios.create方法可以创建一个新的,微型的axios函数对象,axios.create创建出来的函数对象具备了默认axios函数对象的大部分能力,比如axios函数发送AJAX,axios.METHOD发送AJAX,axios默认配置设置。但是需要注意的是axios.create创建的axios函数不等价于默认的axios函数,而是小于默认的axios函数。

axios.create(config)接收一个config配置对象入参,我们可以通过config设置新建axios函数对象的默认配置。

const orderAxios = axios.create({
    baseURL: 'http://localhost:3000/order'
})

const userAxios = axios.create({
    baseURL: 'http://localhost:3000/user'
})

orderAxios({
    url: '/query',
    method: 'get',
    params: {
        id: '123456'
    }
})

userAxios({
    url: '/create',
    method: 'post',
    data: {
        name: 'qfc',
        age: 18
    }
})

或者也可以通过axios.defaults来设置默认配置。

axios请求配置

axios(config), axios.get(url, config) ,axios.post(url, data, config),axios.default.configItem 都涉及到config请求配置对象。

我们已经知道了,可以通过config配置对象设置如下信息:

  • url                请求URL
  • method        请求METHOD
  • headers       请求HEADER
  • data             请求BODY(post,patch,put)
  • params        请求URL的查询字符串参数(get,delete)
  • baseURL     请求URL的默认前缀

那么还有哪些请求配置,它们的作用又是啥呢?

{
  // `url` 是用于请求的服务器 URL
  url: '/user',

  // `method` 是创建请求时使用的方法
  method: 'get', // 默认是 get

  // `baseURL` 将自动加在 `url` 前面,除非 `url` 是一个绝对 URL。
  // 它可以通过设置一个 `baseURL` 便于为 axios 实例的方法传递相对 URL
  baseURL: 'https://some-domain.com/api/',

  // `transformRequest` 允许在向服务器发送前,修改请求数据
  // 只能用在 'PUT', 'POST' 和 'PATCH' 这几个请求方法
  // 后面数组中的函数必须返回一个字符串,或 ArrayBuffer,或 Stream
  transformRequest: [function (data) {
    // 对 data 进行任意转换处理

    return data;
  }],

  // `transformResponse` 在传递给 then/catch 前,允许修改响应数据
  transformResponse: [function (data) {
    // 对 data 进行任意转换处理

    return data;
  }],

  // `headers` 是即将被发送的自定义请求头
  headers: {'X-Requested-With': 'XMLHttpRequest'},

  // `params` 是即将与请求一起发送的 URL 参数
  // 必须是一个无格式对象(plain object)或 URLSearchParams 对象
  params: {
    ID: 12345
  },

  // `paramsSerializer` 是一个负责 `params` 序列化的函数
  // (e.g. https://www.npmjs.com/package/qs, http://api.jquery.com/jquery.param/)
  paramsSerializer: function(params) {
    return Qs.stringify(params, {arrayFormat: 'brackets'})
  },

  // `data` 是作为请求主体被发送的数据
  // 只适用于这些请求方法 'PUT', 'POST', 和 'PATCH'
  // 在没有设置 `transformRequest` 时,必须是以下类型之一:
  // - string, plain object, ArrayBuffer, ArrayBufferView, URLSearchParams
  // - 浏览器专属:FormData, File, Blob
  // - Node 专属: Stream
  data: {
    firstName: 'Fred'
  },

  // `timeout` 指定请求超时的毫秒数(0 表示无超时时间)
  // 如果请求话费了超过 `timeout` 的时间,请求将被中断
  timeout: 1000,

  // `withCredentials` 表示跨域请求时是否需要使用凭证
  withCredentials: false, // 默认的

  // `adapter` 允许自定义处理请求,以使测试更轻松
  // 返回一个 promise 并应用一个有效的响应 (查阅 [response docs](#response-api)).
  adapter: function (config) {
    /* ... */
  },

  // `auth` 表示应该使用 HTTP 基础验证,并提供凭据
  // 这将设置一个 `Authorization` 头,覆写掉现有的任意使用 `headers` 设置的自定义 `Authorization`头
  auth: {
    username: 'janedoe',
    password: 's00pers3cret'
  },

  // `responseType` 表示服务器响应的数据类型,可以是 'arraybuffer', 'blob', 'document', 'json', 'text', 'stream'
  responseType: 'json', // 默认的

  // `xsrfCookieName` 是用作 xsrf token 的值的cookie的名称
  xsrfCookieName: 'XSRF-TOKEN', // default

  // `xsrfHeaderName` 是承载 xsrf token 的值的 HTTP 头的名称
  xsrfHeaderName: 'X-XSRF-TOKEN', // 默认的

  // `onUploadProgress` 允许为上传处理进度事件
  onUploadProgress: function (progressEvent) {
    // 对原生进度事件的处理
  },

  // `onDownloadProgress` 允许为下载处理进度事件
  onDownloadProgress: function (progressEvent) {
    // 对原生进度事件的处理
  },

  // `maxContentLength` 定义允许的响应内容的最大尺寸
  maxContentLength: 2000,

  // `validateStatus` 定义对于给定的HTTP 响应状态码是 resolve 或 reject  promise 。如果 `validateStatus` 返回 `true` (或者设置为 `null` 或 `undefined`),promise 将被 resolve; 否则,promise 将被 rejecte
  validateStatus: function (status) {
    return status >= 200 && status < 300; // 默认的
  },

  // `maxRedirects` 定义在 node.js 中 follow 的最大重定向数目
  // 如果设置为0,将不会 follow 任何重定向
  maxRedirects: 5, // 默认的

  // `httpAgent` 和 `httpsAgent` 分别在 node.js 中用于定义在执行 http 和 https 时使用的自定义代理。允许像这样配置选项:
  // `keepAlive` 默认没有启用
  httpAgent: new http.Agent({ keepAlive: true }),
  httpsAgent: new https.Agent({ keepAlive: true }),

  // 'proxy' 定义代理服务器的主机名称和端口
  // `auth` 表示 HTTP 基础验证应当用于连接代理,并提供凭据
  // 这将会设置一个 `Proxy-Authorization` 头,覆写掉已有的通过使用 `header` 设置的自定义 `Proxy-Authorization` 头。
  proxy: {
    host: '127.0.0.1',
    port: 9000,
    auth: : {
      username: 'mikeymike',
      password: 'rapunz3l'
    }
  },

  // `cancelToken` 指定用于取消请求的 cancel token
  // (查看后面的 Cancellation 这节了解更多)
  cancelToken: new CancelToken(function (cancel) {
  })
}

其中比较常用的有timeout,即设置请求超时时间,默认为0,表示无超时限制。

transformRequest,transformResponse我感觉不太好用,而且完全可以被拦截器替代。

另外可能用到的配置就是validateStatus,默认情况下axios将HTTP响应状态码200~299的情况视为成功,其他情况视为失败。

还有一个cancelToken配置,用于取消axios发出的AJAX请求的,后面介绍。

axios正常响应结果

axios收到HTTP响应也是异步的,但是axios将HTTP响应都封装进了一个对象中,并且将该对象作为了axios函数同步返回的Promise对象的结果。

axios.post('http://localhost:3000/test', {
  name: 'qfc',
  age: 18
}).then(response => {
  console.log(response);
})

当异步的HTTP响应被axios收到时,axios的同步返回的Promise对象就会从pending状态变为fulfilled或rejected。当正常响应时,默认响应状态码在200~299时,Promise对象状态变为fulfilled,当异常响应时,即响应状态码不在200~299时,Promise对象状态变为rejected。

此时,我们讨论的是fulfilled状态Promise对象的PromiseResult结果值,即上面代码then第一个回调函数的入参response值

 response是一个对象,它的结构为:

  • status                响应状态码
  • statusText          响应状态描述
  • headers             响应头
  • data                   响应体
  • config                axios函数对象的配置对象
  • request              axios内部实际发送AJAX请求的XMLHttpRequest实例

从响应结果的结构设计来看,axios严格按照了HTTP响应报文的结构划分了HTTP实际响应内容,同时也加入了axios发送AJAX内部实现的关键的两个参数xhr实例(request),以及xhr实例的配置信息(config)。

axios异常响应结果

我们知道axios会将HTTP响应封装进axios同步返回的Promise对象的结果中,我们可以通过Promise对象调用then方法来获得其正常响应结果,也可以通过catch方法来获得其异常响应结果。

axios.post('http://localhost:3000/test', {
    name: 'qfc',
    age: 18
}).then(response => {
    console.log(response)
}).catch(error => {
    console.log(error.response)
})

axios根据axios.defaults.validateStatus来判断HTTP响应是否正常,默认情况下,当HTTP响应状态码在200~299时,视为正常响应,否则视为异常响应。

如下是error.response的打印信息

 可以发现可以正常响应结果对象的结构一致。

axios拦截器

axios相较于其他AJAX封装库的特别之处就在于axios拦截器,这玩意和nodejs的中间件设计类似,都是做的切面工作。

axios的拦截器分为:请求拦截器和响应拦截器

axios.interceptor.request和axios.interceptor.response

在默认的axios函数,或者axios.create新建axios函数上都有一个属性interceptors,该属性指向一个对象,结构如下

在axios.interceptors对象下有两个属性request,response,它们分别对应请求拦截器和响应拦截器。

axios.interceptors.request和axios.interceptors.response都是对象,都是InterceptorManger的实例。

InterceptorManager.prototype.use

InterceptorManager类的原型上有一个use方法,它的作用就是注册拦截器函数。

InterceptorManager.prototype.use(fulfilled, rejected)

use方法需要传入两个函数,第一个函数用于拦截成功的处理结果,第二个函数用于拦截失败的处理结果。

而axios.interceptors.request/response是InterceptorManager的实例,所以可以使用use方法

而在请求拦截器上use,就是注册请求拦截器,在响应拦截器上use就是注册响应拦截器

axios.interceptors.request.use(config => {
    config.headers['Content-Type'] = 'application/json'
    return Promise.resolve(config)
}, err => {
    return Promise.reject(err)
})

axios.interceptors.response.use(response => {
    return Promise.resolve(response.data)
}, err => {
    return Promise.reject(err)
})

axios.post('http://localhost:3000/test', {
    name: 'qfc',
    age: 18
})

其实,我们很容易想要请求拦截器和响应拦截器拦截的是啥,请求拦截器拦截的是请求,而在axios的请求其实就是config配置对象,响应拦截器拦截的是响应,而axios的响应其实就是axios返回Promise对象的结果值response。

如上代码,请求拦截器fulfilled函数入参是config,我们可以在axios发送AJAX请求前,对config做一些修改。响应拦截器fulfilled函数入参是response,我们可以在axios(config).then收到response前,对response做一些修改。

另外还需要注意的是,关于拦截器通过use注册的fulfilled函数和rejected函数的返回结果类型,这里要求是Promise类型。对于fulfilled函数来说,返回要求是fulfilled状态的Promise对象,对于rejected函数来说,返回要求是rejected状态的Promis对象。

当然,我们也可以让axios帮我们生成Promise对象,因为如果我们return非Promise类型的值都会被包装为fulfilled状态的Promise对象,如果我们thorw一个值,则该值会被包装为rejected状态的Promise对象。

axios.interceptors.request.use(config => {
    config.headers['Content-Type'] = 'application/json'
    return config
}, err => {
    throw err
})

axios.interceptors.response.use(response => {
    return response.data
}, err => {
    throw err
})

axios.post('http://localhost:3000/test', {
    name: 'qfc',
    age: 18
}).then(response => {
  console.log(response);
}).catch(err => {
  console.log(err.response)
})

拦截器的执行顺序

还有一点需要注意的是,可以注册多个请求拦截器和响应拦截器,但是多个拦截器之间的执行顺序需要分析下。

首先明确顺序的是:

[请求拦截器] 优先于 [AJAX请求发送] 优先于 [AJAX响应收到] 优先于 [响应拦截器] 优先于 [axios().then]

但是多个请求拦截器之间的执行顺序,以及多个响应拦截器之间的执行顺序是啥呢?请看下面示例:

axios.interceptors.request.use(config => {
    console.log('请求拦截器1');
    return config
}, err => {
    throw err
})

axios.interceptors.request.use(config => {
    console.log('请求拦截器2');
    return config
}, err => {
    throw err
})

axios.interceptors.response.use(response => {
    console.log('响应拦截器1');
    return response
}, err => {
    throw err
})

axios.interceptors.response.use(response => {
    console.log('响应拦截器2');
    return response
}, err => {
    throw err
})

axios.post('http://localhost:3000/test', {
    name: 'qfc',
    age: 18
}).then(response => {
  console.log('axios.then');
}).catch(err => {
})

可以发现请求拦截器的执行顺序和其注册顺序相反,响应拦截器的执行顺序和其注册顺序相同。 

axios取消请求

我们回顾下XMLHttpRequest如何取消AJAX请求

const xhr = new XMLHttpRequest()

xhr.open('get', 'http://localhost:3000/test')

xhr.responseType = 'json'

xhr.send()

xhr.onreadystatechange = function(){
    if(xhr.readyState === 4) {
        if(xhr.status >= 200 && xhr.status < 300) {
            console.log(xhr.response)
        }
    }
}

xhr.abort() // 取消xhr发送的AJAX请求

XMLHttpRequest实例可以调用abort方法来取消已经发送AJAX请求,在服务器还没有响应成功前。如果服务器已经响应成功了,则xhr.abort()无效。

axios虽然是基于XMLHttpRequest实现的AJAX,但是axios对XMLHttpRequest做了较为全面的封装,使用者无法在服务器响应成功之前获得axios底层的xhr实例,(ps:当服务器响应成功后,axios会将底层xhr实例加入到response.request中返回)

所以我们使用axios时,无法直接使用xhr.abort()来取消已经发送的AJAX请求。

但是axios为我们提供了另外一种很绕的方式来取消已发送的AJAX请求。我们看下具体示例:

let c;

axios({
    url: 'http://localhost:3000/test',
    method: 'post',
    data: {
        name: 'qfc',
        age: 18
    },
    cancelToken: new axios.CancelToken(cancel => {
        c = cancel
    })
}).then(response => {
  console.log(response);
}).catch(err => {
  console.log(err.response || err.message);
})

setTimeout(c, 1000, '我来终止请求') // 服务器2000ms后响应

走读以上代码,我们可以分析出,当1s后,c函数执行,触发了AJAX请求的取消操作。

我们可以猜想c函数应该控制着axios底层的xhr.abort()的执行。

然后,有两个cancelToken和CancelToken,其中

cancelToken是config配置对象的一个配置属性,用于接收一个CancelToken实例。

而CancelToken是默认axios函数对象上的一个属性,它是一个构造函数,接收一个使用者指定的executor执行器函数作为参数,axios内部会调用executor执行器函数,并传入c函数,给使用者。

也就是说:axios想在AJAX发送请求时,但是AJAX响应还没到前,将xhr.abort的控制权交给使用者,但是没有任何可用出口。即axios在收到AJAX响应前,只能单方面的接收用户的输入,而无法输出东西给用户。

所以axios只能在用户输入上做文章了。

即用户输入的config配置对象中,提供一个 new CancelToken(executor) 给axios内部,而其中executor是用户指定的函数,该函数的执行体由用户设计,但是该函数的调用由axios操控,所以axios可以通过函数入参将xhr.abort控制权(cancel)交给用户设计的executor函数执行体中。

用户设计的函数执行体,可以顺势将xhr.abort控制权再次转移出去 c = cancel。

很绕,思路也很巧妙,axios发送AJAX请求中间但凡有一个异步操作包裹,axios这种骚操作就费了。

下面一节,我们来深入axios源码。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

程序员阿甘

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值