axios源码分析

axios源码分析(代码有删减,只针对主要过程)

一、axios的使用

// config对象
axios.request(url, config) || axios.request(config)
axios.get(url, config) || axios.get(config)
axios.delete(url, config) || axios.delete(config)
axios.head(url, config) || axios.head(config)
axios.options(url, config) || axios.options(config)
axios.post(url, data, config) || axios.post(config)
axios.put(url, data, config) || axios.put(config)
axios.patch(url, data, config) || axios.patch(config)

1、以上的请求方式实际上都是最后调用Axios的request方法

2、browser环境==>axios.get==>Axios.prototype.request==>dispatchRequest==>knownAdapters['xhr']==> new XMLHttpRequest()

3、node环境用的是knownAdapters['http']

4、用json-server来创建接口(json-server --watch db.json --delay 2000 延时2秒)

二、axios的创建过程(源码)

// node_modules\axios\lib\axios.js

import defaults from './defaults/index.js';
import Axios from './core/Axios.js';

function createInstance(defaultConfig) {
    /**
     * 依据默认配置defaults生成Axios的实例
     * Axios上面有request和getUri原型方法
     * ['delete', 'get', 'head', 'options']、['post', 'put', 'patch']用utils.forEach扩展到Axios.prototype上
     * 以及defaults和interceptors实例属性
     */
    const context = new Axios(defaultConfig);
    /**
     * instance为Axios.prototype.request的一个wrap函数(包装函数)
     */
    const instance = bind(Axios.prototype.request, context);

    /**
     * 第一个utils.extend并不多余
     * 因为其内部用于扩展的遍历forEach用的Object.getOwnPropertyNames(obj)和Object.keys(obj)
     * 这2种对象遍历都不会将context上的Axios.prototype的属性给遍历
     * 所以需要单独加Axios.prototype的扩展
     */
    // 将Axios.prototype上的所有属性扩展到instance上
    utils.extend(instance, Axios.prototype, context, { allOwnKeys: true });

    // 将context上的属性扩展到instance上
    utils.extend(instance, context, null, { allOwnKeys: true });

    // 创建新instance的工厂函数(axios可以使用create方法创建新的instance)
    instance.create = function create(instanceConfig) {
        // 参数为默认参数和自定义参数的合并
        return createInstance(mergeConfig(defaultConfig, instanceConfig));
    };

    return instance;
}

// 创建一个默认配置的instance,用于导出
const axios = createInstance(defaults);

// 下面扩展一些属性到axios上(instance和axios的区别就在这里)
axios.Axios = Axios;

// Expose Cancel & CancelToken
axios.CanceledError = CanceledError;
axios.CancelToken = CancelToken; // 取消请求
axios.isCancel = isCancel;
axios.VERSION = VERSION; // axios的版本
axios.toFormData = toFormData;

// Expose AxiosError class
axios.AxiosError = AxiosError;

// alias for CanceledError for backward compatibility
axios.Cancel = axios.CanceledError;

// Expose all/spread
axios.all = function all(promises) {
  return Promise.all(promises);
};

axios.spread = spread;

// Expose isAxiosError
axios.isAxiosError = isAxiosError;

// Expose mergeConfig
axios.mergeConfig = mergeConfig;

axios.AxiosHeaders = AxiosHeaders;

axios.formToJSON = thing => formDataToJSON(utils.isHTMLForm(thing) ? new FormData(thing) : thing);

axios.HttpStatusCode = HttpStatusCode;

axios.default = axios;

// this module should only have a default export
export default axios
// node_modules\axios\lib\core\Axios.js
class Axios {
  constructor(instanceConfig) {
    this.defaults = instanceConfig;
    this.interceptors = {
      /**
      * new InterceptorManager(),实例化InterceptorManager
      * 得到的实例上有handlers实例属性和use、eject、clear、forEach原型方法
      * 拦截器就是调用interceptors的request(response)上的use方法,将拥有2个回调的对象推进handlers数组中
      * 在下面的request方法中,使用其对应forEach方法将上面的2个回调推入了InterceptorChain中
      */
      request: new InterceptorManager(),
      response: new InterceptorManager()
    };
  }

  request(configOrUrl, config) {
    if (typeof configOrUrl === 'string') { // 针对第一个参数是路径字符串的情形,都转成config对象形式
      config = config || {};
      config.url = configOrUrl;
    } else {
      config = configOrUrl || {};
    }

    config = mergeConfig(this.defaults, config);

    // 确定请求方法,默认为自定义的method,无就取默认配置的method,无就默认为get方法,并统一转化为小写
    config.method = (config.method || this.defaults.method || 'get').toLowerCase();

    let contextHeaders;

    // Flatten headers
    contextHeaders = headers && utils.merge(
      headers.common,
      headers[config.method]
    );

    contextHeaders && utils.forEach(
      ['delete', 'get', 'head', 'post', 'put', 'patch', 'common'],
      (method) => {
        delete headers[method];
      }
    );

    config.headers = AxiosHeaders.concat(contextHeaders, headers);

    const requestInterceptorChain = [];
    /**
    * 使请求拦截器requestInterceptorChain内的函数由while遍历进行同步执行
    * 后面的dispatchRequest和responseInterceptorChain内的函数还是通过promise组成微任务进行异步执行
    * 此时不需要在dispatchRequest后面加一个占位的undefined
    */
    let synchronousRequestInterceptors = true;
    this.interceptors.request.forEach(function unshiftRequestInterceptors(interceptor) {
      if (typeof interceptor.runWhen === 'function' && interceptor.runWhen(config) === false) {
        return;
      }
	  // 默认情况下 interceptor.synchronous ==> false
      synchronousRequestInterceptors = synchronousRequestInterceptors && interceptor.synchronous;
	  // 将请求拦截器use函数的参数推进requestInterceptorChain数组
      requestInterceptorChain.unshift(interceptor.fulfilled, interceptor.rejected);
    });

    const responseInterceptorChain = [];
    this.interceptors.response.forEach(function pushResponseInterceptors(interceptor) {
      // 将响应拦截器use函数的参数推进responseInterceptorChain数组
      responseInterceptorChain.push(interceptor.fulfilled, interceptor.rejected);
    });

    let promise;
    let i = 0;
    let len;

    if (!synchronousRequestInterceptors) {
      // 请求拦截器requestInterceptorChain内的函数异步执行的话
      const chain = [dispatchRequest.bind(this), undefined];
      chain.unshift.apply(chain, requestInterceptorChain);
      chain.push.apply(chain, responseInterceptorChain);
      len = chain.length;

      promise = Promise.resolve(config); // 返回一个成功的promise对象(第一个请求拦截器必定会执行成功的回调)
	  // axios拦截器interceptors的原理
        /**
         * Axios.prototype.request得到的是三个then的微任务函数
         * 前两个是请求前的处理函数
         * 中间是发送xhr请求和一个占位undefined,也就是在这个地方,开始了xhr的四部曲
         * 最后是response拦截器的两个函数
        */
        /**
         * (1) 前一个then中的代码都是同步执行的,执行结束后第二个then即可注册进入微任务队列。
         * (2) 当前一个then中有return 关键字,需要return的内容完全执行结束,第二个then才会注册进入微任务队列。
        */
      while (i < len) {
        promise = promise.then(chain[i++], chain[i++]);
      }

      return promise;
    }
	
    // 请求拦截器requestInterceptorChain内的函数同步执行的话
    len = requestInterceptorChain.length;

    let newConfig = config;

    i = 0;
	// 直接遍历执行requestInterceptorChain里面的onFulfilled函数
    while (i < len) {
      const onFulfilled = requestInterceptorChain[i++];
      const onRejected = requestInterceptorChain[i++];
      try {
        newConfig = onFulfilled(newConfig);
      } catch (error) {
        onRejected.call(this, error);
        break;
      }
    }

    try {
      // 这里传入了最新的经过请求拦截器处理的配置项newConfig
      promise = dispatchRequest.call(this, newConfig);
    } catch (error) {
      return Promise.reject(error);
    }

    i = 0;
    len = responseInterceptorChain.length;

    while (i < len) {
      promise = promise.then(responseInterceptorChain[i++], responseInterceptorChain[i++]);
    }

    return promise;
  }
}

// 将['delete', 'get', 'head', 'options']内的方法扩展到Axios.prototype
utils.forEach(['delete', 'get', 'head', 'options'], function forEachMethodNoData(method) {
  Axios.prototype[method] = function(url, config) {
    return this.request(mergeConfig(config || {}, {
      method,
      url,
      data: (config || {}).data
    }));
  };
});

// 将['post', 'put', 'patch']内的方法扩展到Axios.prototype
utils.forEach(['post', 'put', 'patch'], function forEachMethodWithData(method) {
  function generateHTTPMethod(isForm) {
    return function httpMethod(url, data, config) {
      return this.request(mergeConfig(config || {}, {
        method,
        headers: isForm ? {
          'Content-Type': 'multipart/form-data'
        } : {},
        url,
        data
      }));
    };
  }
  Axios.prototype[method] = generateHTTPMethod();
});

export default Axios;
// node_modules\axios\lib\core\InterceptorManager.js
class InterceptorManager {
  constructor() {
    // 存放包含use方法的2个参数函数的对象的数组
    this.handlers = [];
  }

  use(fulfilled, rejected, options) {
    // 调用use方法,将拦截器的2个参数函数的对象推进handlers
    this.handlers.push({
      fulfilled,
      rejected,
      synchronous: options ? options.synchronous : false,
      runWhen: options ? options.runWhen : null
    });
    return this.handlers.length - 1;
  }
  
  // 将某个拦截器的元素置为空
  eject(id) {
    if (this.handlers[id]) {
      this.handlers[id] = null;
    }
  }
  // 清空handlers
  clear() {
    if (this.handlers) {
      this.handlers = [];
    }
  }
  
  // 将handlers里面的每个元素执行一遍forEachHandler
  forEach(fn) {
    utils.forEach(this.handlers, function forEachHandler(h) {
      if (h !== null) {
        fn(h);
      }
    });
  }
}

export default InterceptorManager;

三、axios的创建过程(手写简化版)

function Axios(config) {
    this.defaults = config;
    this.interceptors = { // 简化拦截器
        request: {},
        response: {}
    }
}
// 直接将原型方法写上去
Axios.prototype.request = function(config) { // 这里不要写成箭头函数,不然this为undefined,后面的bind也不起作用
    console.log('发送ajax方法' + '请求的类型为' + config.method);
}
Axios.prototype.get = function(config) {
    return this.request({method: 'get'});
}
Axios.prototype.post = function(config) {
    return this.request({method: 'post'});
}

function createInstance(defaultsConfig) {
    // 此时可以context.get()、context.post(),但是不能context()
    let context = new Axios(defaultsConfig);
    // 要想axios可以自调用,instance就应该是一个函数,此时instance不能.调用,因为上面没有相应的方法,需要进行扩展
    let instance = Axios.prototype.request.bind(context);
    Object.keys(Axios.prototype).forEach(ls => { // 将原型上的属性扩展上去
        instance[ls] = Axios.prototype[ls].bind(context);
    })
    Object.keys(context).forEach(ls => { // 将defaults和interceptors扩展上去(Object.keys不会遍历到prototype上)
        instance[ls] = context[ls];
    })
    return instance;
}

let axios = createInstance();
// axios({method: 'post'})
// axios.get();
// axios.post();
export default axios;

四、模拟实现axios发送请求(手写简化版)

// 1、声明构造函数
class Axios {
    constructor(config) {
        this.defaults = config;
        this.interceptors = {
            request: {},
            response: {}
        }
    }
    request(url, config) {
        if (typeof url === 'string') {
            config.url = url;
        } else {
            config = url;
        }
        // 创建一个成功的promise对象
        let promise = Promise.resolve(config);
        // 创建一个数组
        let chains = [dispatchRequest, undefined]; // undefined占位
        let len = chains.length;
        let i = 0;
        while(i < len) {
            // 这里第一次循环由于上面返回成功的promise对象,所以一定会执行
            promise = promise.then(chains[i++], chains[i++])
        }
        return promise;
    }
}



// 2、dispatchRequest函数
function dispatchRequest(config) {
    // 调用适配器发送请求
    return xhrAdapter(config).then(res => {
        // 对响应的结果进行转换
        return JSON.parse(res.data)
    }, err => {
        console.log('err', err);
    })
}

// 3、xhrAdapter适配器
function xhrAdapter(config) {
    return new Promise((resolve, reject) => {
        // 发送ajax请求
        let xhr = new XMLHttpRequest();
        xhr.open(config.method, config.url);
        xhr.send();
        xhr.onreadystatechange = function() {
            if (xhr.readyState === 4) {
                if (xhr.status >= 200 && xhr.status < 300) {
                    // 成功的状态
                    resolve({
                        config: config,
                        data: xhr.response,
                        headers: xhr.getAllResponseHeaders(),
                        request: xhr,
                        status: xhr.status,
                        statusText: xhr.statusText
                    });
                } else {
                    // 失败的状态
                    reject(new Error('失败的状态码为' + xhr.status));
                }
            }
        }
    })
}

// 4、创建axios函数
let axios = Axios.prototype.request.bind(null);

axios({
    method: 'get',
    url: 'http://localhost:3000/goodsList'
}).then(res => {
    console.log('res==>', res);
});

五、interceptor拦截器工作原理(手写简化版)

// 1、声明构造函数
class Axios {
    constructor(config) {
        this.defaults = config;
        this.interceptors = {
            request: new InterceptorManager(), // 主要提供了use方法来收集拦截器的执行函数
            response: new InterceptorManager()
        }
    }
    request(url, config) {
        // 将多种形式的传参改为配置对象的形式
        if (typeof url === 'string') {
            config.url = url;
        } else {
            config = url;
        }
        // 创建一个成功的promise对象
        let promise = Promise.resolve(config);
        // 创建一个数组(里面放入真正的请求方法和一个占位的undefined)
        let chains = [dispatchRequest.bind(this, config), undefined];
        // 遍历请求拦截器里面的放有fulfilled和rejected函数的对象数组,将其unshift到chains的前面
        if (this.interceptors.request.handlers.length) {
            this.interceptors.request.handlers.forEach(ls => {
                // [1, 2, 3].unshift(4, 5) ==> [4, 5, 1, 2, 3]
                chains.unshift(ls.fulfilled, ls.rejected);
            })
        }
        // 遍历响应拦截器里面的放有fulfilled和rejected函数的对象数组,将其push到chains的后面
        if (this.interceptors.response.handlers.length) {
            this.interceptors.response.handlers.forEach(ls => {
                chains.push(ls.fulfilled, ls.rejected);
            })
        }
        let len = chains.length;
        let i = 0;
        while(i < len) {
            // 这里第一次循环由于上面返回成功的promise对象,所以一定会执行
            // 然后组成promise.then链
            // 前一个成功,后一个才可注册进入微任务队列
            promise = promise.then(chains[i++], chains[i++])
        }
        return promise;
    }
}
// 在Axios的原型上加上get和post方法
Axios.prototype.get = function(config) {
    return this.request(config);
}
Axios.prototype.post = function(config) {
    return this.request(config);
}

class InterceptorManager {
    constructor() {
        this.handlers = [];
    }
    use(fulfilled, rejected, options) {
        this.handlers.push({
            fulfilled,
            rejected,
            synchronous: options ? options.synchronous : false,
            runWhen: options ? options.runWhen : null
        })
    }
    eject(id) {
        if (this.handlers[id]) {
            this.handlers[id] = null;
        }
    }
    clear() {
        if (this.handlers) {
            this.handlers = [];
        }
    }
}

// 2、dispatchRequest函数
function dispatchRequest(config) {
    // 调用适配器发送请求
    return xhrAdapter(config).then(res => {
        console.log('结果==>', JSON.parse(res.data));
        // 对响应的结果进行转换
        return JSON.parse(res.data);
    }, err => {
        console.log('err', err);
    })
}

// 3、xhrAdapter适配器
function xhrAdapter(config) {
    return new Promise((resolve, reject) => {
        // 发送ajax请求
        let xhr = new XMLHttpRequest();
        xhr.open(config.method, config.url);
        xhr.send();
        xhr.onreadystatechange = function() {
            if (xhr.readyState === 4) {
                if (xhr.status >= 200 && xhr.status < 300) {
                    // 成功的状态
                    resolve({
                        config: config,
                        data: xhr.response,
                        headers: xhr.getAllResponseHeaders(),
                        request: xhr,
                        status: xhr.status,
                        statusText: xhr.statusText
                    });
                } else {
                    // 失败的状态
                    reject(new Error('失败的状态码为' + xhr.status));
                }
            }
        }
    })
}

// 4、创建axios函数
function createInstance(config) {
    let context = new Axios(config);
    let instance = Axios.prototype.request.bind(context);
    Object.keys(Axios.prototype).forEach(ls => {
        instance[ls] = Axios.prototype[ls].bind(context);
    })
    Object.keys(context).forEach(ls => {
        instance[ls] = context[ls];
    })
    return instance;
}

let axios = createInstance();
axios.interceptors.request.use(function requestFulfilledOne(config) {
    console.log('request fulfilled One');
    return config;
}, function requestRejectedOne() {
    console.log('request rejected One');
})
axios.interceptors.request.use(function requestFulfilledTwo(config) {
    console.log('request fulfilled Two');
    return config;
}, function requestRejectedTwo() {
    console.log('request rejected Two');
})
axios.interceptors.response.use(function responseFulfilledOne(response) {
    console.log('response fulfilled One');
    return response;
}, function responseRejectedOne() {
    console.log('response rejected One');
})
axios.interceptors.response.use(function responseFulfilledTwo(response) {
    console.log('response fulfilled Two');
    return response;
}, function responseRejectedTwo() {
    console.log('response rejected Two');
})

// axios({
//     method: 'get',
//     url: 'http://localhost:3000/goodsList'
// }).then(res => {
//     console.log('res==>', res);
// });
axios.get({
    method: 'get',
    url: 'http://localhost:3000/goodsList'
}).then(res => {
    console.log('res==>', res);
});

六、取消请求功能(手写简化版)

<!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">
    <title>Document</title>
    <script>
        window.onload = function () {
            function Axios(config) {
                this.config = config;
            }

            Axios.prototype.request = function (config) {
                return dispatchRequest(config);
            }

            function dispatchRequest(config) {
                return xhrAdapter(config);
            }

            function xhrAdapter(config) {
                return new Promise((resolve, reject) => {
                    // 发送ajax请求
                    let xhr = new XMLHttpRequest();
                    xhr.open(config.method, config.url);
                    xhr.send();
                    xhr.onreadystatechange = function () {
                        if (xhr.readyState === 4) {
                            if (xhr.status >= 200 && xhr.status < 300) {
                                // 成功的状态
                                resolve({
                                    config: config,
                                    data: xhr.response,
                                    headers: xhr.getAllResponseHeaders(),
                                    request: xhr,
                                    status: xhr.status,
                                    statusText: xhr.statusText
                                });
                            } else {
                                // 失败的状态
                                reject(new Error('失败的状态码为' + xhr.status));
                            }
                        }
                    }
                    if (config.cancelToken) {
                        config.cancelToken.promise.then(() => {
                            xhr.abort();
                        })
                    }
                })
            }

            const context = new Axios({});
            let axios = Axios.prototype.request.bind(context);

            function CancelToken(executor) {
                let resolvePromise;
                this.promise = new Promise((resolve, reject) => {
                    resolvePromise = resolve;
                })
                executor(function () {
                    resolvePromise();
                })
            }

            let cancel = null; // 设置变量
            let dom1 = document.querySelector('#btn1');
            let dom2 = document.querySelector('#btn2');
            /**
             * CancelToken的参数为executor,new操作执行CancelToken函数
             * 执行executor时,将其的参数函数赋给cancel变量
             * 所以cancel执行时,就会将CancelToken里面的this.promise置为成功的promise对象
             * 此时xhrAdapter的config.cancelToken.promise.then就可以执行xhr.abort()来中止请求
             * 
            */
            let cancelToken = new CancelToken(function(c) {
                cancel = c;
            });
            dom1.onclick = function() { // 发起请求
                axios({
                    method: 'get',
                    url: 'http://localhost:3000/goodsList',
                    cancelToken, // 设置取消请求的配置项
                }).then(res => {
                    cancel = null;
                    console.log('res===>', res);
                })
            }
            dom2.onclick = function () { // 取消请求
                if (cancel !== null) {
                    cancel();
                }
            }
        }
    </script>
</head>

<body>
    <button id="btn1">发起请求</button>
    <button id="btn2">取消请求</button>
</body>

</html>

七、相关问题

1、axios和Axios的关系?
1、从语法上来说,axios不是Axios的实例
2、在功能上,axios是Axios的实例
3、axios是Axios.prototype.request函数经过bind返回的函数
4、axios作为对象,有Axios原型上的所有方法,也有Axios对象上的所有属性
2、instance和axios的区别?
相同点:
	1、都是一个可以发起任意请求的函数
	2、都有发特定请求的各种方法
	3、都有默认配置defaults和拦截器属性interceptors

不同点:
	1、默认配置可能不一样
	2、axios在instance的基础上多了很多配置(CancelToken、all......)
3、axios的整体运行流程
发起请求:axios() ===> request(config) ===> dispatchRequest(config) ===> xhrAdapter(config)
数据响应:xhrAdapter(config) ===> dispatchRequest(config) ===> request(config) ===> axios().then(res => {})
4、axios的请求和响应拦截器是什么?
请求拦截器:
	1、是真正发送请求前执行的回调函数
	2、可对请求进行检查或配置进行特定处理
	3、成功的回调函数,传递的默认是config
	4、成功的回调函数,传递的默认是config
响应拦截器:
	1、在请求得到响应后执行的回调函数
	2、可以对响应数据进行特定处理
	3、成功的回调函数,传递的默认是response
	4、成功的回调函数,传递的默认是error
5、axios的请求和响应数据转化器是什么?
请求转换器:对请求头和请求体数据进行特定处理的函数
	if (utils.isObject(data)) {
		setContentTypeIfUnset(headers, 'application/json;charset=utf-8');
		return JSON.stringify(data);
	}
响应转换器:将响应体json字符串解析为js对象或者数组的函数
	response.data = JSON.parse(response.data);
6、response的整体结构
{
	data,
	status,
	statusText
    headers,
    config,
    request
}
7、error的整体结构
{
	message,
	response,
	request
}
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值