• axios 源码简析


    axios 简介

    Axios 是一个基于 promise 的网络请求库,可以用于浏览器和 node.js。懂的都懂!

    本文将从接下来的几个点,分析 axios:

    • 创建实例
    • 请求发送
    • 请求取消

    创建实例

    axios.js文件中会初始调用一次createInstance函数。在此函数中会new Axios创建一个名为context的实例和使用bind函数对Axios.prototype.request进行包裹并返回名为instance的函数。

    然后,将Axios.prototypecontext的属性和方法extendinstance函数。最后,在instance上添加create方法。此方法是一个工厂方法,能够创建新的Axios实例。

    源码 lib/axios.js

    function createInstance(defaultConfig) {var context = new Axios(defaultConfig);var instance = bind(Axios.prototype.request, context);// Copy axios.prototype to instanceutils.extend(instance, Axios.prototype, context);// Copy context to instanceutils.extend(instance, context);// Factory for creating new instancesinstance.create = function create(instanceConfig) {return createInstance(mergeConfig(defaultConfig, instanceConfig));};return instance;
    }
    
    // Create the default instance to be exported
    var axios = createInstance(defaults); 
    
    • 1
    • 2
    • 3
    • 4
    • 5

    通过上述步骤后,axios 变量可以通过axios({})发送请求,也可以通过axios.get/post等方式发起请求(get/post方法都是Axios.prototype上的方法)。

    请求发送

    我们调用的axiox.get/post等方法,其实都是去调用的Axios.prototype.request方法。在此方法中会合并配置、执行请求拦截器和响应拦截器等。

    合并配置

    源码 lib/core/Axios.js

    Axios.prototype.request = function request(configOrUrl, config) {// 此处支持两种 api 的调用方式,axios('example/url'[, config]) 和 axios(config)if (typeof configOrUrl === 'string') {config = config || {};config.url = configOrUrl;} else {config = configOrUrl || {};}// 合并创建实例时传入的默认配置和目前传入的配置config = mergeConfig(this.defaults, config);...
    } 
    
    • 1
    • 2

    注意通常我们在使用 axios 时会通过axios.defaults[name] = value来设置一些默认配置,作用于每一个请求。但是必须要在通过 axios.create 工厂方法创建实例之前设置。

    // 正确用法
    axios.defaults['timeout'] = 3000;
    console.log('axios.defaults', JSON.parse(JSON.stringify(axios.defaults)));
    
    const instance = axios.create({});
    console.log('instance.defaults', JSON.parse(JSON.stringify(instance.defaults))); 
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6

    // 错误用法
    const instance = axios.create({});
    console.log('instance.defaults', JSON.parse(JSON.stringify(instance.defaults)));
    
    axios.defaults['timeout'] = 3000;
    console.log('axios.defaults', JSON.parse(JSON.stringify(axios.defaults))); 
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6

    请求拦截器

    请求拦截器在我们日常开发中经常会用到。比如说我们在发送请求前记录发送时间(记录接口响应所花的时间),或者添加业务 token 之类的。请求拦截器目前分为两类:异步拦截器同步拦截器

    源码 lib/core/Axios.js

    // 收集请求拦截器
    
    // 请求拦截器数组
    var requestInterceptorChain = [];
    // 请求拦截器同步还是异步执行
    var synchronousRequestInterceptors = true;
    this.interceptors.request.forEach(function unshiftRequestInterceptors(interceptor) {synchronousRequestInterceptors = synchronousRequestInterceptors && interceptor.synchronous;requestInterceptorChain.unshift(interceptor.fulfilled, interceptor.rejected);
    }); 
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8

    异步拦截器

    源码 lib/core/Axios.js

    var promise;
    
    // 请求拦截器异步流程
    if (!synchronousRequestInterceptors) {var chain = [dispatchRequest, undefined];// chain:[onfulfilled, onRejected, ..., dispatchRequest, undefined]Array.prototype.unshift.apply(chain, requestInterceptorChain);promise = Promise.resolve(config);while (chain.length) {promise = promise.then(chain.shift(), chain.shift());}return promise;
    } 
    
    • 1
    • 2
    • 3
    • 4
    • 5

    通过 promise 的链式调用来实现异步请求拦截器。生成的 promise 链如下:

    var promise = Promise.resolve(config);
    promise.then("请求拦截器函数2", "请求拦截器异常处理函数2").then("请求拦截器函数1", "请求拦截器异常处理函数1")... // 可能有多个请求拦截器,也可能没有.then(dispatchRequest, undefined).then("请求成功处理函数", "请求失败处理函数") 
    
    • 1
    • 2

    但是这样的方式也带了问题:

    1.如果有请求拦截器的话,位于 promise 链中的第一个异常处理函数,永远都不会执行。(如上面的请求拦截器异常处理函数2)
    2.如果主线程阻塞,ajax 请求会被延迟发送。(涉及 event Loop,可以去单独了解)

    示例代码

    axios.interceptors.request.use((config) => {return config;
    }, (err) => {return Promise.reject(err);
    });
    
    // 将被延迟发送
    axios.get('https://httpbin.org/get');
    
    function manualRequest() {// 同步发送const xhr = new XMLHttpRequest();xhr.open('get', 'https://httpbin.org/get');xhr.send();
    }
    
    manualRequest();
    
    // 模拟耗时操作
    let x = 100000000;
    while (x > 0) {x--;
    } 
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16

    同步拦截器

    源码 lib/core/Axios.js

    // 请求拦截器同步流程
    var newConfig = config;
    while (requestInterceptorChain.length) {var onFulfilled = requestInterceptorChain.shift();var onRejected = requestInterceptorChain.shift();try {newConfig = onFulfilled(newConfig);} catch (error) {onRejected(error);break;}
    }
    
    try {promise = dispatchRequest(newConfig);
    } catch (error) {return Promise.reject(error);
    }
    
    return promise; 
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10

    同步拦截器就比较简单了。同时也没上面异步拦截器的问题。

    示例代码

    axios.interceptors.request.use((config) => {return config;
    }, (err) => {return Promise.reject(err);// 使用同步拦截器
    }, {synchronous: true});
    
    axios.get('https://httpbin.org/get');
    
    function manualRequest() {const xhr = new XMLHttpRequest();xhr.open('get', 'https://httpbin.org/get');xhr.send();
    }
    
    manualRequest();
    
    // 模拟耗时操作
    let x = 100000000;
    while (x > 0) {x--;
    } 
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15

    dispatchRequest

    源码 lib/core/dispatchRequest.js

    function dispatchRequest(config) {
     // 确定发送请求的模块var adapter = config.adapter || defaults.adapter;return adapter(config).then(function onAdapterResolution(response) {return response;}, function onAdapterRejection(reason) {return Promise.reject(reason);});
    }; 
    
    • 1
    • 2
    • 3

    在这个函数中通过适配器模式确定发送请求的模块,然后发出请求。

    adapter

    源码 lib/defaults/index.js

    function getDefaultAdapter() {var adapter;if (typeof XMLHttpRequest 
    } 
    
    • 1
    • 2

    通过适配器模式,识别不同的环境,兼容多种格式。对外提供统一的接口。

    响应拦截器

    源码 lib/core/Axios.js

    var responseInterceptorChain = [];
    this.interceptors.response.forEach(function pushResponseInterceptors(interceptor) {responseInterceptorChain.push(interceptor.fulfilled, interceptor.rejected);
    });
    
    if (!synchronousRequestInterceptors) {var chain = [dispatchRequest, undefined];Array.prototype.unshift.apply(chain, requestInterceptorChain);chain = chain.concat(responseInterceptorChain);promise = Promise.resolve(config);while (chain.length) {promise = promise.then(chain.shift(), chain.shift());}return promise;
    } 
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6

    响应拦截器也是通过 promise 的链式调用来实现的。生成的 promise 链如下:

    var promise = Promise.resolve(config);
    promise.then(dispatchRequest, undefined).then("响应拦截器1", "响应拦截器异常处理函数1").then("响应拦截器2", "响应拦截器异常处理函数2").then("请求成功处理函数", "请求失败处理函数") 
    
    • 1
    • 2

    请求取消

    axios 支持两种取消请求的方式。CancelToken 和 AbortController。在 v0.22.0 后开始,不推荐使用 CancelToken。

    Cancel Token

    源码 lib/cancel/CancelToken.js、lib/adapters/xhr.js

    // CancelToken.js
    function CancelToken(executor) {var resolvePromise;this.promise = new Promise(function promiseExecutor(resolve) {resolvePromise = resolve;});var token = this;this.promise.then(function(cancel) {if (!token._listeners) return;var i = token._listeners.length;while (i-- > 0) {token._listeners[i](cancel);}token._listeners = null;});executor(function cancel(message) {if (token.reason) {// Cancellation has already been requestedreturn;}token.reason = new CanceledError(message);resolvePromise(token.reason);});
    }
    
    CancelToken.prototype.subscribe = function subscribe(listener) {if (this.reason) {listener(this.reason);return;}if (this._listeners) {this._listeners.push(listener);} else {this._listeners = [listener];}
    };
    
    CancelToken.source = function source() {var cancel;var token = new CancelToken(function executor(c) {cancel = c;});return {token: token,cancel: cancel};
    };
    
    // xhr.js
    if (config.cancelToken) {onCanceled = function(cancel) {if (!request) {return;}reject(!cancel || (cancel && cancel.type) ? new CanceledError() : cancel);request.abort();request = null;};config.cancelToken && config.cancelToken.subscribe(onCanceled);
    } 
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13

    示例代码

    const CancelToken = axios.CancelToken;
    const source = CancelToken.source();
    
    axios.get('/user/12345', {cancelToken: source.token
    }).catch(function (thrown) {if (axios.isCancel(thrown)) {console.log('Request canceled', thrown.message);} else {// 处理错误}
    });
    
    // 取消请求(message 参数是可选的)
    source.cancel('Operation canceled by the user.'); 
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9

    取消请求的流程比较复杂:

    1.通过const source = CancelToken.source();生成一个 source 对象,source 对象中 token 属性是一个CancelToken实例,cancel 是取消 token 代表的 promise 实例的函数。
    2.将 source 对象的 token 传递给请求的 config 中。
    3.在xhr.js中,如果判断 config.cancelToken 为真,将 onCanceled 函数订阅到 source.token 对象的_listeners数组中。
    4.当调用 source.cancel() 时,将 source.token 对象内的 promise 状态改为 fulfilled,然后执行 then 函数,遍历 source.token 对象的_listeners数组,然后执行每一个 onCanceled 函数。
    5.执行 onCanceled 函数,将 XMLHttpRequest 实例对象 reject 和 abort 请求。

    AbortController

    源码 lib/adapters/xhr.js

    if (config.cancelToken || config.signal) {onCanceled = function(cancel) {if (!request) {return;}reject(!cancel || (cancel && cancel.type) ? new CanceledError() : cancel);request.abort();request = null;};if (config.signal) {config.signal.aborted ? onCanceled() : config.signal.addEventListener('abort', onCanceled);}
    } 
    
    • 1
    • 2

    示例代码

    const controller = new AbortController();
    
    axios.get('/foo/bar', {signal: controller.signal
    }).then(function(response) {//...
    });
    // 取消请求
    controller.abort() 
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7

    AbortController的取消请求流程相对简单一点:

    1.通过const controller = new AbortController();生成一个 controller 对象。
    2.将 controller 对象的 singal 传递给请求的 config 中。
    3.在xhr.js中,如果判断 config.singal 为真,将订阅 config.singal 的 abort 事件。
    4.当执行 controller.abort() 时,触发订阅的 abort 事件。执行 onCanceled 函数,将 XMLHttpRequest 实例对象 reject 和 abort 请求。

    目前只看了这么多,以后有时间再完善!

  • 相关阅读:
    Codeforces Round #809 (Div. 2)
    Git 笔记
    第一个微信小程序的诞生
    js中数组去重
    LeetCode_排序_中等_791.自定义字符串排序
    python 基于PHP+MySQL的学生成绩管理系统
    CMT2380F32模块开发7-reset例程
    Python图像处理【1】图像与视频处理
    OpenCV入门2:OpenCV中的图像表示
    C++虚继承原理与类布局分析
  • 原文地址:https://blog.csdn.net/web22050702/article/details/126054645