Axios 是一个基于 promise 的 HTTP 库,可以用在浏览器和 node.js 中。
- 支持 Promise API
- 拦截请求和响应
- 转换请求数据和响应数据
说白了,axios就是使用Promise封装的XHR。
想要复刻我们首先要知道使用方法,如果你还不会使用axios,具体使用方法这里就不多说了,可以去官网补课一下。
axios和axios.method
首先实现axios是一个方法,参数形式有三种,url、url+option、option,这里需要通过arguments来获取参数,然后判断参数的长度和类型进行不同的处理,这里就写一个大概的逻辑
然后,axios的返回值是一个Promise对象,所以函数的返回结果需要使用new Promise
包装一下
Axios.prototype.request = function () { // 处理参数(粗略) const args = Array.from(arguments); let config = {} if (args.length === 0) { throw new Error('[axios]参数不能为空') } else if (args.length === 1) { if (typeof args[0] === 'string') { config.url = args[0]; config.method = 'get' } else if (Object.prototype.toString.call(args[0]) === '[object Object]') { config = args[0] } else { throw new Error('[axios]参数格式错误') } } else { if (typeof args[0] === 'string' && Object.prototype.toString.call(args[1]) === '[object Object]') { config = args[1] config.url = args[0] } } return new Promise((resolve, reject) => { const { url = '', method = 'get', data = {} } = config; // 发送ajax请求 const xhr = new XMLHttpRequest(); xhr.open(method, url, true); xhr.onload = function () { if (xhr.status == 200) resolve(xhr.responseText); else reject(xhr.responseText); } xhr.send(data); }) } 复制代码
axios的核心方法request已经完成了,接下来进行包装
处理,在使用时axios是一个方法,而不是一个实例,也就是说我们需要将request方法导出
function createAxiosInstance() { const axios = new Axios(); let request = axios.request.bind(axios); return request; } const axios = createAxiosInstance(); 复制代码
至于为什么要用这么绕的方法来处理,到最后你就懂了。
现在我们先来测试一下我们刚才的杰作,首先起一个server,为了方便就用express吧
const express = require('express') const app = express() app.all('*', function (req, res, next) { res.header('Access-Control-Allow-Origin', '*'); res.header('Access-Control-Allow-Headers', 'Content-Type'); res.header('Access-Control-Allow-Methods', '*'); res.header('Content-Type', 'application/json;charset=utf-8'); next(); }); app.get('/data', function (request, response) { data = { name: 'king', age: 36, sex: 'man', hobby: 'code' }; response.json(data); }); app.listen(5005, () => { console.log('server start and listen port 5005'); }) 复制代码
然后编写html页面,引入编写的axios文件,然后编写函数进行请求
<!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8"> <meta http-equiv="X-UA-Compatible" content="IE=edge"> <meta name="viewport" content="width=device-width, initial-scale=1.0"> <script src="./axios.js"></script> <title>axios</title> </head> <body> <button type="button" onclick="getData()">axios</button> <script> // function getData() { // axios({ // url: 'http://localhost:5005/data', // method: 'GET' // }).then(res => { // console.log(res); // }) // } function getData() { axios('http://localhost:5005/data').then(res => { console.log(res); }) } </script> </body> </html> 复制代码
打开浏览器,测试一下结果,perfect
然后是axios.method,源码对于axios.method的处理还是挺有意思的
通过遍历方法名来对Axios原型上添加方法
// 只需要url const single = ['delete', 'get', 'head', 'options']; // 可以发送请求体 const double = ['post', 'put', 'patch']; single.forEach(method => { Axios.prototype[method] = function () { return this.request({ method, url: arguments[0], ...arguments[1] || {} }) } }) double.forEach(method => { Axios.prototype[method] = function () { return this.request({ method, url: arguments[0], data: arguments[1] || {}, ...arguments[2] || {} }) } }) 复制代码
这样就在原型上添加个get、post等方法,但是注意!!!我们导出的axios并不是Axios的实例,而是一个request方法,所以我们需要将Axios原型上的方法添加到导出的request上
写一个工具函数,负责转移方法
/** * @description: 将第二个对象的自有属性添加到第一个属性 * @param {object} a 第一个对象 * @param {object} b 第二个对象 * @param {object} thisArg this指向 * @return {void} */ function extendUtil(a, b, thisArg) { for (let key in b) { if (b.hasOwnProperty(key)) { if (typeof b[key] === 'function') { a[key] = b[key].bind(thisArg); } else { a[key] = b[key] } } } } 复制代码
再修改一下createInstance函数
function createAxiosInstance() { const axios = new Axios(); let request = axios.request.bind(axios); extendUtil(request, Axios.prototype, axios) return request; } 复制代码
这样我们就将axios.method实现出来,再来测试一下
<script> function getData() { axios.get('http://localhost:5005/data').then(res => { console.log(res); }) } </script> 复制代码
再次成功
拦截器
拦截器也是axios的特色之一,可以在请求发出之前和接收到响应之后进行某些操作来处理数据。
拦截器的使用方式如下
// 添加请求拦截器 axios.interceptors.request.use(function (config) { // 在发送请求之前做些什么 return config; }, function (error) { // 对请求错误做些什么 return Promise.reject(error); }); // 添加响应拦截器 axios.interceptors.response.use(function (response) { // 对响应数据做点什么 return response; }, function (error) { // 对响应错误做点什么 return Promise.reject(error); }); 复制代码
通过使用方式我们可以看出,axios上还挂载着interceptors,其拥有request和response两个属性,通过use方法来添加拦截器
我们可以这样实现:创建一个InterceptorsManage构造函数,原型上添加use方法,axios的interceptors上挂载着两个这样的实例
function InterceptorsManage() { this.handlers = []; } InterceptorsManage.prototype.use = function (fulfilled, rejected) { this.handlers.push({ fulfilled, rejected }) } function Axios(options) { this.interceptors = { request: new InterceptorsManage(), response: new InterceptorsManage() } } function createAxiosInstance() { const axios = new Axios(); const request = axios.request.bind(axios); extendUtil(request, Axios.prototype, axios); extendUtil(request, axios); return request; } 复制代码
现在我们已经实现了axios.interceptors.response.use这种结构,然后我们再来完善他的内部功能,首先我们把request的核心方法分离出来,为了方便拦截器的顺序调用
Axios.prototype.request = function (...args) { let chain = [sendAjax.bind(this), undefined] // 请求拦截 this.interceptors.request.handlers.forEach(interceptor => { chain.unshift(interceptor.fulfilled, interceptor.rejected) }) // 响应拦截 this.interceptors.response.handlers.forEach(interceptor => { chain.push(interceptor.fulfilled, interceptor.rejected) }) let promise = Promise.resolve(...args); while (chain.length > 0) { promise = promise.then(chain.shift(), chain.shift()) } return promise; } 复制代码
拦截器两两一对,分别是操作和异常处理,将ajax请和undefined作为一对,请求拦截器的处理添加到ajax请求前面,响应拦截添加到后面,然后顺序执行,直到调用链中没有函数
let promise = Promise.resolve(...args);
用来传递参数,可以将处理结果一直传递到ajax请求,然后把ajax的响应作为参数继续传递给响应拦截,然后才把最终结果返回
我们再来测试一下,在html中添加拦截器
<script> axios.interceptors.request.use(function (config) { // 在发送请求之前做些什么 config.url += '?a=1' return config; }, function (error) { // 对请求错误做些什么 return Promise.reject(error); }); // 添加响应拦截器 axios.interceptors.response.use(function (response) { console.log(response.name); // 对响应数据做点什么 return response; }, function (error) { // 对响应错误做点什么 return Promise.reject(error); }); function getData() { axios.get('http://localhost:5005/data').then(res => { console.log(res); }) } </script> 复制代码
在浏览器中看一下结果
大功告成