- 本文参加了由公众号@若川视野 发起的每周源码共读活动, 点击了解详情一起参与。
1.准备工作
git clone https://github.com/axios/axios.git
# 也可以使用我带注释的Axios源码
git clone https://github.com/crush2020/myAxios-1.1.3.git
cd axios
npm install
npm run start
# open [http://localhost:3000](http://localhost:3000)
# chrome F12 source 控制面板 webpack// . lib 目录下,根据情况自行断点调试
如果代码没有用es语法进行重构下面这一步可以省略
当运行start命令时会发现终端报错__dirname未定义(当前模块的目录名),这是因为最新的代码全部用EsModule规范去写了,就会不识别node默认的全局变量__dirname,这个时候就要进行修改,用 **CommonJS规范给的api去模拟这个变量,修改为如下项目就可以跑起来了。
第一种
import url from 'url';
import path from 'path';
const __filename = url.fileURLToPath(import.meta.url)
const __dirname = path.dirname(__filename);
或者第二种
import { dirname } from "node:path"
import { fileURLToPath } from "node:url"
const __filename = fileURLToPath(import.meta.url)
const __dirname = dirname(__filename);
let server;
function pipeFileToResponse(res, file, type) {
if (type) {
res.writeHead(200, {
'Content-Type': type
});
}
fs.createReadStream(path.join(__dirname, file)).pipe(res);
}
修改sandbox/server.js文件,让调试时可以看到源文件
if (pathname === '/index.html') {
pipeFileToResponse(res, './client.html');
} else if (pathname === '/axios.js') {
pipeFileToResponse(res, '../dist/axios.js', 'text/javascript');
} else if (pathname === '/axios.js.map')
// 原来的代码,因为打包后的映射文件文件叫axios.js.map,判断这里也要改
// pipeFileToResponse(res, '../dist/axios.map', 'text/javascript');
pipeFileToResponse(res, '../dist/axios.js.map', 'text/javascript');
}
2.axios 结构是怎样的
打开 http://localhost:3000,在控制台打印出axios,估计很多人都没打印出来看过。
console.log({axios: axios});
层层点开来看,axios 的结构是怎样的,先有一个大概印象。
3.Axios源码
看源码第一步,先看package.json。一般都会申明 main 主入口文件。
// package.json
{
"name": "axios",
"version": "1.1.3",
"description": "Promise based HTTP client for the browser and node.js",
"main": "index.js",
// ...
}
主入口文件
// index.js
module.exports = require('./lib/axios');
3.1 lib/axios.js主文件
axios.js文件 代码相对比较多。分为三部分展开叙述。
- 第一部分:引入一些工具函数
utils、Axios构造函数、默认配置defaults等。 - 第二部分:是生成实例对象
axios、axios.Axios、axios.create等。 - 第三部分取消相关API,
cancelToken,signal的实现,还有all、spread、导出等实现。
3.1.1 第一部分
引入一些工具函数utils、Axios构造函数、默认配置defaults,一些方法等。这些方法我会在讲解对应的代码时,进行说明
// 第一部分:
// lib/axios
// 严格模式
'use strict';
// 引入 utils 对象,有很多工具方法。
import utils from './utils.js';
// 引入 bind 方法
import bind from './helpers/bind.js';
// 核心构造函数 Axios
import Axios from './core/Axios.js';
// 合并配置方法
import mergeConfig from './core/mergeConfig.js';
// 引入默认配置
import defaults from './defaults/index.js';
// 引入一些方法
import formDataToJSON from './helpers/formDataToJSON.js';
import CanceledError from './cancel/CanceledError.js';
import CancelToken from './cancel/CancelToken.js';
import isCancel from './cancel/isCancel.js';
import {VERSION} from './env/data.js';
import toFormData from './helpers/toFormData.js';
import AxiosError from './core/AxiosError.js';
import spread from './helpers/spread.js';
import isAxiosError from './helpers/isAxiosError.js';
bind()函数
'use strict';
// 返回一个新的wrap函数,产生一个闭包
// 所以函数被调用时的this始终为传入的this
export default function bind(fn, thisArg) {
return function wrap() {
return fn.apply(thisArg, arguments);
};
}
forEach()函数
遍历数组和对象。设计模式称之为迭代器模式。很多源码都有类似这样的遍历函数。
/**
* @param {Object|Array} 需要遍历的数据
* @param {Function} 传入的函数
*
* @param {Boolean} [allOwnKeys = false] 是否遍历可迭代属性
* @returns {void}
*/
function forEach(obj, fn, {allOwnKeys = false} = {}) {
// Don't bother if no value provided
// 无值返回
if (obj === null || typeof obj === 'undefined') {
return;
}
let i;
let l;
// Force an array if not already something iterable
// 不是对象放在数组里
if (typeof obj !== 'object') {
/*eslint no-param-reassign:0*/
obj = [obj];
}
if (isArray(obj)) {
// Iterate over array values
// 是数组便利整个数组,并使用call调用函数,并把value,index,arr传入fn函数
for (i = 0, l = obj.length; i < l; i++) {
fn.call(null, obj[i], i, obj);
}
} else {
// Iterate over object keys
//getOwnPropertyNames方法返回一个由指定对象的所有自身属性的属性名
//(包括不可枚举属性但不包括 Symbol 值作为名称的属性)组成的数组。
// keys方法会返回一个由一个给定对象的自身可枚举属性组成的数组
// 判断allOwnKeys,有就调用getOwnPropertyNames,没有就调用keys
const keys = allOwnKeys ? Object.getOwnPropertyNames(obj) : Object.keys(obj);
const len = keys.length;
let key;
// 遍历整个keys,并使用call调用函数,并把value,key,obj传入fn函数
for (i = 0; i < len; i++) {
key = keys[i];
fn.call(null, obj[key], key, obj);
}
}
}
utils.extend()函数
/**
* @param {Object} a 要扩展的对象
* @param {Object} b 被复制的对象
* @param {Object} thisArg 函数被调用的this
*
* @param {Boolean} [allOwnKeys]
* @returns {Object} The resulting value of object a
*/
// 其实就是遍历参数 b 对象,复制到 a 对象上,如果是函数就是则调用 bind 处理。
const extend = (a, b, thisArg, {allOwnKeys}= {}) => {
forEach(b, (val, key) => {
if (thisArg && isFunction(val)) {
a[key] = bind(val, thisArg);
} else {
a[key] = val;
}
}, {allOwnKeys});
return a;
}
3.1.2 第二部分
是生成实例对象 axios、axios.Axios、axios.create等。
/**
* Create an instance of Axios
*
* @param {Object} defaultConfig The default config for the instance
*
* @returns {Axios} A new instance of Axios
*/
function createInstance(defaultConfig) {
// 创建一个新的Axios实例
const context = new Axios(defaultConfig);
const instance = bind(Axios.prototype.request, context);
// Copy axios.prototype to instance
// 复制 Axios.prototype 到实例instance上。
// 也就是为什么 有 axios.get 等别名方法,
// 且调用的是 Axios.prototype.get 等别名方法。
utils.extend(instance, Axios.prototype, context, {allOwnKeys: true});
// Copy context to instance
// 复制 context 到 intance 实例
// 也就是为什么默认配置 axios.defaults 和拦截器 axios.interceptors 可以使用的原因
// 其实是new Axios().defaults 和 new Axios().interceptors
utils.extend(instance, context, null, {allOwnKeys: true});
// Factory for creating new instances
// 工厂模式 创建新的实例 用户可以自定义一些参数
instance.create = function create(instanceConfig) {
return createInstance(mergeConfig(defaultConfig, instanceConfig));
};
return instance;
}
// Create the default instance to be exported
// 创建默认实例
const axios = createInstance(defaults);
// Expose Axios class to allow class inheritance
// 暴露 Axios calss 允许 class 继承
axios.Axios = Axios;
3.1.3 第三部分
取消相关API实现(这个在后面详细介绍),还有all、spread、公开Axios错误信息类,导出等实现。
// Expose Cancel & CancelToken
// 取消相关API实现
axios.CanceledError = CanceledError;
axios.CancelToken = CancelToken;
axios.isCancel = isCancel;
axios.VERSION = VERSION;
axios.toFormData = toFormData;
// Expose AxiosError class
// 公开Axios错误信息类
axios.AxiosError = AxiosError;
// alias for CanceledError for backward compatibility
// 用于向后兼容的CanceledError的别名
axios.Cancel = axios.CanceledError;
// Expose all/spread
axios.all = function all(promises) {
return Promise.all(promises);
};
// 就是利用apply把数组形式的参数转为一个个参数传入
axios.spread = spread;
// Expose isAxiosError
// 公开isAxiosError类
axios.isAxiosError = isAxiosError;
// 数据转换
axios.formToJSON = thing => {
return formDataToJSON(utils.isHTMLForm(thing) ? new FormData(thing) : thing);
};
export default axios
3.2核心构造函数 Axios
class Axios {
constructor(instanceConfig) {
// 默认配置
this.defaults = instanceConfig;
// 拦截器对象
this.interceptors = {
request: new InterceptorManager(),
response: new InterceptorManager()
};
}
/**
* Dispatch a request
* 核心方法
* @param {String|Object} configOrUrl The config specific for this request
* (merged with this.defaults)
* @param {?Object} config
*
* @returns {Promise} The Promise to be fulfilled
*/
request(configOrUrl, config) {
// 核心方法后面单独讲
...
}
// 这是获取 Uri 的函数,处理为正常请求的url,并把编码的解码
getUri(config) {
config = mergeConfig(this.defaults, config);
const fullPath = buildFullPath(config.baseURL, config.url);
return buildURL(fullPath, config.params, config.paramsSerializer);
}
}
// Provide aliases for supported request methods
// 提供一些请求方法的别名
// 遍历执行
// 也就是为啥我们可以 axios.get 等别名的方式调用,而且调用的还是 Axios.prototype.request 方法
// 这个也在上面的 axios 结构图上有所体现。
utils.forEach(['delete', 'get', 'head', 'options'], function forEachMethodNoData(method) {
/*eslint func-names:0*/
Axios.prototype[method] = function(url, config) {
return this.request(mergeConfig(config || {}, {
method,
url,
data: (config || {}).data
}));
};
});
utils.forEach(['post', 'put', 'patch'], function forEachMethodWithData(method) {
/*eslint func-names:0*/
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();
Axios.prototype[method + 'Form'] = generateHTTPMethod(true);
});
3.3拦截器构造函数 InterceptorManager
如何使用:
// Add a request interceptor
// 添加请求前拦截器
axios.interceptors.request.use(function (config) {
return config;
}, function (error) {
return Promise.reject(error);
},{ synchronous: true, runWhen: onGetCall }
);
// Add a response interceptor
// 添加请求后拦截器
axios.interceptors.response.use(function (response) {
return response;
}, function (error) {
return Promise.reject(error);
});
// 也可以在创建的实例上使用
const instance = axios.create();
instance.interceptors.request.use(function () {/*...*/});
//移除
const myInterceptor = axios.interceptors.request.use(function () {/*...*/});
axios.interceptors.request.eject(myInterceptor);
源码实现
'use strict';
import utils from './../utils.js';
class InterceptorManager {
constructor() {
//handles 用于存储拦截器函数。
this.handlers = [];
}
/**
* Add a new interceptor to the stack
*
* @param {Function} fulfilled The function to handle `then` for a `Promise`
* @param {Function} rejected The function to handle `reject` for a `Promise`
*
* @return {Number} An ID used to remove interceptor later
*/
// 传递三个函数作为参数,fulfilled成功的回调函数,rejected失败的回调函数
// options额外参数,synchronous使拦截器同步运行,只有当runWhen()的返回为false时,才会执行拦截器
// 返回数字 ID,用于移除拦截器。
use(fulfilled, rejected, options) {
this.handlers.push({
fulfilled,
rejected,
synchronous: options ? options.synchronous : false,
runWhen: options ? options.runWhen : null
});
return this.handlers.length - 1;
}
/**
* Remove an interceptor from the stack
*
* @param {Number} id The ID that was returned by `use`
*
* @returns {Boolean} `true` if the interceptor was removed, `false` otherwise
*/
// 根据传入的id移除拦截器,及清空handlers对应的数据
eject(id) {
if (this.handlers[id]) {
this.handlers[id] = null;
}
}
/**
* Clear all interceptors from the stack
*
* @returns {void}
*/
// 清空整个拦截器
clear() {
if (this.handlers) {
this.handlers = [];
}
}
/**
* Iterate over all the registered interceptors
* This method is particularly useful for skipping over any
* interceptors that may have become `null` calling `eject`.
* @param {Function} fn The function to call for each interceptor
* @returns {void}
*/
// 遍历执行所有拦截器,传入fn
forEach(fn) {
utils.forEach(this.handlers, function forEachHandler(h) {
if (h !== null) {
fn(h);
}
});
}
}
export default InterceptorManager;
3.4实例结合
上文叙述的调试时运行npm start 是用axios/sandbox/client.html路径的文件作为示例的,读者可以自行调试。以下是一段这个文件中的代码
axios(options)
.then(function (res) {
response.innerHTML = JSON.stringify(res.data, null, 2);
error.innerHTML = "None";
})
.catch(function (res) {
error.innerHTML = JSON.stringify(res.toJSON(), null, 2)
console.error('Axios caught an error from request', res.toJSON());
response.innerHTML = JSON.stringify(res.data, null, 2);
});
3.4.1调用栈流程
如果不想一步步调试,有个偷巧的方法。
知道 axios 使用了XMLHttpRequest。
可以在项目中搜索:new XMLHttpRequest。
定位到文件 axios/lib/adapters/xhr.js
在这条语句 let request = new XMLHttpRequest();
chrome 浏览器中 打个断点调试下,再根据调用栈来细看具体函数等实现。
Call Stack
dispatchXhrRequest (xhr.js:65)
xhrAdapter (xhr.js:46)
dispatchRequest (dispatchRequest.js:46)
configOrUrl (Axios.js.140)
wrap (bind.js:5)
submit.onclick ((index):145)
简述下流程:
Send Request按钮点击submit.onclick- 调用
axios函数实际上是调用Axios.prototype.request函数,而这个函数使用bind返回的一个名为wrap的函数。 - 处理请求传入的参数configOrUrl
- (有请求拦截器的情况下执行请求拦截器),中间会执行
dispatchRequest方法 dispatchRequest之后调用adapter (xhrAdapter)- 最后调用
Promise中的函数dispatchXhrRequest,(有响应拦截器的情况下最后会再调用响应拦截器) - 如果有取消逻辑将会在请求发送后执行
3.5Axios.prototype.request 请求核心方法
/**
* Dispatch a request
*
* @param {String|Object} configOrUrl The config specific for this request (merged with this.defaults)
* @param {?Object} config
*
* @returns {Promise} The Promise to be fulfilled
*/
request(configOrUrl, config) {
/*eslint no-param-reassign:0*/
// Allow for axios('example/url'[, config]) a la fetch API
//判断第一个参数是字符串,则设置 url,也就是支持axios('example/url', [, config]),也支持axios({})。
if (typeof configOrUrl === 'string') {
config = config || {};
config.url = configOrUrl;
} else {
config = configOrUrl || {};
}
// .合并默认参数和用户传递的参数
config = mergeConfig(this.defaults, config);
const {transitional, paramsSerializer} = config;
if (transitional !== undefined) {
validator.assertOptions(transitional, {
silentJSONParsing: validators.transitional(validators.boolean),
forcedJSONParsing: validators.transitional(validators.boolean),
clarifyTimeoutError: validators.transitional(validators.boolean)
}, false);
}
if (paramsSerializer !== undefined) {
validator.assertOptions(paramsSerializer, {
encode: validators.function,
serialize: validators.function
}, true);
}
// Set config.method
//设置请求的方法,默认是是get方法
config.method = (config.method || this.defaults.method || 'get').toLowerCase();
// Flatten headers
//处理单个请求的自定义headers
const defaultHeaders = config.headers && utils.merge(
config.headers.common,
config.headers[config.method]
);
defaultHeaders && utils.forEach(
['delete', 'get', 'head', 'post', 'put', 'patch', 'common'],
function cleanHeaderConfig(method) {
delete config.headers[method];
}
);
config.headers = new AxiosHeaders(config.headers, defaultHeaders);
// filter out skipped interceptors
// 过滤掉跳过的其拦截器
// 存储过滤后的请求拦截器
const requestInterceptorChain = [];
// 是否异步执行构造器,初始值为true
let synchronousRequestInterceptors = true;
this.interceptors.request.forEach(function unshiftRequestInterceptors(interceptor) {
// 当拦截器的options的runWhen为函数且返回的结果为falses时跳出循环
if (typeof interceptor.runWhen === 'function' && interceptor.runWhen(config) === false) {
return;
}
// 当拦截器的options的synchronous === true 时,才为同步执行,否则为异步
synchronousRequestInterceptors = synchronousRequestInterceptors && interceptor.synchronous;
// 把请求拦截器器插入到数组开头,这就是为什么后写的请求拦截器先执行的原因
requestInterceptorChain.unshift(interceptor.fulfilled, interceptor.rejected);
});
// 存储响应拦截器,
const responseInterceptorChain = [];
this.interceptors.response.forEach(function pushResponseInterceptors(interceptor) {
responseInterceptorChain.push(interceptor.fulfilled, interceptor.rejected);
});
let promise;
let i = 0;
let len;
// 默认时或者synchronous === false时异步执行请求
if (!synchronousRequestInterceptors) {
// 创建队列
const chain = [dispatchRequest.bind(this), undefined];
// 把处理后的拦截器插入任务队列的对应位置
chain.unshift.apply(chain, requestInterceptorChain);
chain.push.apply(chain, responseInterceptorChain);
len = chain.length;
// 给下一个promise传递一个参数config
promise = Promise.resolve(config);
// 按顺序一次执行队列里的任务
while (i < len) {
promise = promise.then(chain[i++], chain[i++]);
}
return promise;
}
// 处理同步时的任务队列
len = requestInterceptorChain.length;
let newConfig = config;
i = 0;
// 没有使用promise封装,保证了任务队列依次执行
while (i < len) {
const onFulfilled = requestInterceptorChain[i++];
const onRejected = requestInterceptorChain[i++];
try {
newConfig = onFulfilled(newConfig);
} catch (error) {
onRejected.call(this, error);
break;
}
}
// 请求拦截同步时,执行的请求,出错抛出异常
try {
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;
}
3.6dispatchRequest 最终派发请求
'use strict';
import transformData from './transformData.js';
import isCancel from '../cancel/isCancel.js';
import defaults from '../defaults/index.js';
import CanceledError from '../cancel/CanceledError.js';
import AxiosHeaders from '../core/AxiosHeaders.js';
/**
* Throws a `CanceledError` if cancellation has been requested.
*
* @param {Object} config The config that is to be used for the request
*
* @returns {void}
*/
// 抛出 错误原因,使`Promise`走向`rejected`
function throwIfCancellationRequested(config) {
if (config.cancelToken) {
config.cancelToken.throwIfRequested();
}
if (config.signal && config.signal.aborted) {
throw new CanceledError();
}
}
/**
* Dispatch a request to the server using the configured adapter.
*
* @param {object} config The config that is to be used for the request
*
* @returns {Promise} The Promise to be fulfilled
*/
export default function dispatchRequest(config) {
// 取消相关
throwIfCancellationRequested(config);
// 确保 headers 存在
config.headers = AxiosHeaders.from(config.headers);
// Transform request data
// 转换请求的数据
config.data = transformData.call(
config,
config.transformRequest
);
// adapter 适配器 真正发送请求
const adapter = config.adapter || defaults.adapter;
return adapter(config).then(
function onAdapterResolution(response) {
throwIfCancellationRequested(config);
// Transform response data
// 转换响应的数据
response.data = transformData.call(
config,
config.transformResponse,
response
);
response.headers = AxiosHeaders.from(response.headers);
return response;
},
function onAdapterRejection(reason) {
// 取消相关
if (!isCancel(reason)) {
throwIfCancellationRequested(config);
// Transform response data
// 转换响应的数据
if (reason && reason.response) {
reason.response.data = transformData.call(
config,
config.transformResponse,
reason.response
);
reason.response.headers = AxiosHeaders.from(reason.response.headers);
}
}
return Promise.reject(reason);
}
);
}
3.7adapter请求适配器
根据当前环境引入,如果是浏览器环境引入xhr,是node环境则引入http。
/**
* If the browser has an XMLHttpRequest object, use the XHR adapter, otherwise use the HTTP
* adapter
*
* @returns {Function}
*/
function getDefaultAdapter() {
let adapter;
if (typeof XMLHttpRequest !== 'undefined') {
// 根据 XMLHttpRequest 判断
// For browsers use XHR adapter
adapter = adapters.getAdapter('xhr');
} else if (typeof process !== 'undefined' && utils.kindOf(process) === 'process') {
// For node use HTTP adapter
// 根据 process 判断
adapter = adapters.getAdapter('http');
}
return adapter;
}
3.8xhr发送请求
接下来就是我们熟悉的 XMLHttpRequest 对象。
主要提醒下:
onabort是请求取消事件,
onreadystatechange函数XMLHttpRequest.onreadystatechange 会在 XMLHttpRequest 的readyState 属性发生改变时触发 readystatechange (en-US) 事件的时候被调用。
XMLHttpRequest.abort()如果请求已被发出,则立刻中止请求。
export default function xhrAdapter(config) {
return new Promise(function dispatchXhrRequest(resolve, reject) {
let requestData = config.data;
const requestHeaders = AxiosHeaders.from(config.headers).normalize();
const responseType = config.responseType;
if (utils.isFormData(requestData) && platform.isStandardBrowserEnv) {
requestHeaders.setContentType(false); // Let the browser set it
}
// eslint-disable-next-line no-debugger
// 创建一个XML
let request = new XMLHttpRequest();
// HTTP basic authentication
// HTTP基本身份验证
if (config.auth) {
const username = config.auth.username || '';
const password = config.auth.password ? unescape(encodeURIComponent(config.auth.password)) : '';
requestHeaders.set('Authorization', 'Basic ' + btoa(username + ':' + password));
}
const fullPath = buildFullPath(config.baseURL, config.url);
// 初始化一个请求。
request.open(config.method.toUpperCase(), buildURL(fullPath, config.params, config.paramsSerializer), true);
// Set the request timeout in MS
// 设置超时时间
request.timeout = config.timeout;
let onCanceled;
// 清除请求
function done() {
if (config.cancelToken) {
config.cancelToken.unsubscribe(onCanceled);
}
if (config.signal) {
config.signal.removeEventListener('abort', onCanceled);
}
}
function onloadend() {
if (!request) {
return;
}
// Prepare the response
const responseHeaders = AxiosHeaders.from(
'getAllResponseHeaders' in request && request.getAllResponseHeaders()
);
const responseData = !responseType || responseType === 'text' || responseType === 'json' ?
request.responseText : request.response;
const response = {
data: responseData,
status: request.status,
statusText: request.statusText,
headers: responseHeaders,
config,
request
};
// 返回结果
settle(function _resolve(value) {
resolve(value);
done();
}, function _reject(err) {
reject(err);
done();
}, response);
// Clean up request
request = null;
}
if ('onloadend' in request) {
// Use onloadend if available
// 使用已配置的onloadend
request.onloadend = onloadend;
} else {
// Listen for ready state to emulate onloadend
// 监听 readyState 改变
request.onreadystatechange = function handleLoad() {
if (!request || request.readyState !== 4) {
return;
}
// The request errored out and we didn't get a response, this will be
// handled by onerror instead
// With one exception: request that using file: protocol, most browsers
// will return status as 0 even though it's a successful request
if (request.status === 0 && !(request.responseURL && request.responseURL.indexOf('file:') === 0)) {
return;
}
// readystate handler is calling before onerror or ontimeout handlers,
// so we should call onloadend on the next 'tick'
//成功时调用onloadend()
setTimeout(onloadend);
};
}
// Handle browser request cancellation (as opposed to a manual cancellation)
// 取消
request.onabort = function handleAbort() {
if (!request) {
return;
}
reject(new AxiosError('Request aborted', AxiosError.ECONNABORTED, config, request));
// Clean up request
request = null;
};
// Handle low level network errors
// 错误
request.onerror = function handleError() {
// Real errors are hidden from us by the browser
// onerror should only fire if it's a network error
reject(new AxiosError('Network Error', AxiosError.ERR_NETWORK, config, request));
// Clean up request
request = null;
};
// Handle timeout
// 超时
request.ontimeout = function handleTimeout() {
let timeoutErrorMessage = config.timeout ? 'timeout of ' + config.timeout + 'ms exceeded' : 'timeout exceeded';
const transitional = config.transitional || transitionalDefaults;
if (config.timeoutErrorMessage) {
timeoutErrorMessage = config.timeoutErrorMessage;
}
reject(new AxiosError(
timeoutErrorMessage,
transitional.clarifyTimeoutError ? AxiosError.ETIMEDOUT : AxiosError.ECONNABORTED,
config,
request));
// Clean up request
request = null;
};
// Add xsrf header 添加xsrf头
// This is only done if running in a standard browser environment.
// Specifically not if we're in a web worker, or react-native.
if (platform.isStandardBrowserEnv) {
// Add xsrf header
const xsrfValue = (config.withCredentials || isURLSameOrigin(fullPath))
&& config.xsrfCookieName && cookies.read(config.xsrfCookieName);
if (xsrfValue) {
requestHeaders.set(config.xsrfHeaderName, xsrfValue);
}
}
// Remove Content-Type if data is undefined
requestData === undefined && requestHeaders.setContentType(null);
// Add headers to the request
if ('setRequestHeader' in request) {
utils.forEach(requestHeaders.toJSON(), function setRequestHeader(val, key) {
request.setRequestHeader(key, val);
});
}
// Add withCredentials to request if needed
if (!utils.isUndefined(config.withCredentials)) {
request.withCredentials = !!config.withCredentials;
}
// Add responseType to request if needed
// 修改响应类型
if (responseType && responseType !== 'json') {
request.responseType = config.responseType;
}
// Handle progress if needed
// 上传下载进度相关
if (typeof config.onDownloadProgress === 'function') {
request.addEventListener('progress', progressEventReducer(config.onDownloadProgress, true));
}
// Not all browsers support upload events
if (typeof config.onUploadProgress === 'function' && request.upload) {
request.upload.addEventListener('progress', progressEventReducer(config.onUploadProgress));
}
// 取消请求相关
if (config.cancelToken || config.signal) {
// Handle cancellation
// eslint-disable-next-line func-names
onCanceled = cancel => {
if (!request) {
return;
}
reject(!cancel || cancel.type ? new CanceledError(null, config, request) : cancel);
request.abort();
request = null;
};
// 如果config.cancelToken === true,执行后边的并返回结果,如果为 False,返回config.cancelToken
config.cancelToken && config.cancelToken.subscribe(onCanceled);
if (config.signal) {
config.signal.aborted ? onCanceled() : config.signal.addEventListener('abort', onCanceled);
}
}
const protocol = parseProtocol(fullPath);
if (protocol && platform.protocols.indexOf(protocol) === -1) {
reject(new AxiosError('Unsupported protocol ' + protocol + ':', AxiosError.ERR_BAD_REQUEST, config));
return;
}
// Send the request
// 发送请求。
request.send(requestData || null);
});
}
3.9 dispatchRequest 之 取消模块
使用方法
// 从v0.22.0开始,Axios支持AbortController以fetch API方式取消请求:
const controller = new AbortController();
axios.get('/foo/bar', {
signal: controller.signal
}).then(function(response) {
//...
});
// cancel the request
controller.abort()
// 从v0.22.0开始,此API已被弃用,不应在新项目中使用,但是最新的代码不知道为啥还支持
const CancelToken = axios.CancelToken;
const source = CancelToken.source();
axios.get('/user/12345', {
cancelToken: source.token
}).then(function (res) {
console.log('Request success', res.message);
}).catch(function (thrown) {
if (axios.isCancel(thrown)) {
console.log('Request canceled', thrown.message);
} else {
// handle error
}
});
source.cancel('Operation canceled by the user.');
CancelToken取消流程(比较绕建议自己多调试几次)
1.调用axios.CancelToken.source()创建对象{ token,canncel }
// 创建一个source时,被调用
static source() {
let cancel;
const token = new CancelToken(function executor(c) {
// 这里的c就是executor函数的参数,cancel函数
cancel = c;
});
return {
token,
cancel
};
}
2.axios.get(conflig)请求开始,由于conflig中含有cancelToken在这里,会在这里调用cancelToken.subscribe把取消函数onCanceled传入任务队列_listeners
// 取消请求相关
if (config.cancelToken || config.signal) {
// Handle cancellation
// eslint-disable-next-line func-names
// 取消实际被调用的函数
onCanceled = cancel => {
// 请求就结束时,取消无效
if (!request) {
return;
}
// 否则就reject出去
reject(!cancel || cancel.type ? new CanceledError(null, config, request) : cancel);
// 调用xml的abort()取消方法
request.abort();
//把这次请求的xml置为空
request = null;
};
// 如果config.cancelToken === true,执行后边的并返回结果
// // cancelToken来自axios.CancelToken
config.cancelToken && config.cancelToken.subscribe(onCanceled);
}
cancelToken.subscribe()函数
subscribe(listener) {
// 判断是否已经取消过了
if (this.reason) {
listener(this.reason);
return;
}
// 判断有无其他的取消任务
if (this._listeners) {
this._listeners.push(listener);
} else {
this._listeners = [listener];
}
}
3.请求发送中,执行下一步代码source.cancel()
axios.get('/user/12345', {
cancelToken: source.token
}).then(function (res) {
console.log('Request success', res.message);
}).catch(function (thrown) {
if (axios.isCancel(thrown)) {
console.log('Request canceled', thrown.message);
} else {
// handle error
}
});
source.cancel('Operation canceled by the user.');
4.执行source.cancel(),等与执行CancelToken.executor()
// 真正取消source.cancel()所调用的函数
executor(function cancel(message, config, request) {
// 是否已经取消
if (token.reason) {
// Cancellation has already been requested
return;
}
// 设置取消信息
token.reason = new CanceledError(message, config, request);
// resolvePromise为this.promise成功的回调函数reslove
//resolvePromise被调用,这个时候 this.promise.then()的reslove就可以被调用了
resolvePromise(token.reason);
});
5.调用resolvePromise(token.reason)传入错误信息对象;等于调用了this.promise成功的回调函数reslove
// 存储promise的成功的resolve回调函数,方便调用,取消时调用
let resolvePromise;
this.promise = new Promise(function promiseExecutor(resolve) {
resolvePromise = resolve;
});
6.this.promise.then()开始执行
// 这里的cancel就是resolvePromise(token.reason)中的token.reason
this.promise.then(cancel => {
// 这里的取消任务队列_listeners就是subscribe()被执行的时候赋值的onCanceled函数
// 判断有取消任务
if (!token._listeners) return;
let i = token._listeners.length;
// 有就把任务拿出来依次执行,并传入错误信息对象token.reason
while (i-- > 0) {
token._listeners[i](cancel);
}
// 执行完后,取消任务队列赋值为空
token._listeners = null;
});
7.执行取消任务队列_listeners中的listeners函数,_listeners为执行cancelToken.subscribe(onCanceled)传入的
onCanceled = cancel => {
// 请求就结束时,取消无效
if (!request) {
return;
}
// 否则就reject出去
reject(!cancel || cancel.type ? new CanceledError(null, config, request) : cancel);
// 调用xml的abort()取消方法
request.abort();
//把这次请求的xml置为空
request = null;
};
8.取消结束
AbortController取消流程
1.创建AbortController对象传入signal属性
只读属性 signal 返回一个 AbortSignal 实例对象,该对象可以根据需要处理 DOM 请求通信,既可以建立通信,也可以终止通信。
// AbortController 接口表示一个控制器对象,允许你根据需要中止一个或多个 Web 请求。
const controller = new AbortController();
options.signal = controller.signal
2.axios.get(conflig)请求开始,由于conflig中含有signal在这里执行函数
// 取消请求相关
if (config.cancelToken || config.signal) {
if (config.signal) {
config.signal.aborted ? onCanceled() : config.signal.addEventListener('abort', onCanceled);
}
}
3.由于请求未被终止过signal.aborted === false,执行上面的取消函数onCanceled(),如果没有也会执行监听abort事件执行取消函数onCanceled()
4.执行onCanceled()
onCanceled = cancel => {
// 请求就结束时,取消无效
if (!request) {
return;
}
// 否则就reject出去
reject(!cancel || cancel.type ? new CanceledError(null, config, request) : cancel);
// 调用xml的abort()取消方法
request.abort();
//把这次请求的xml置为空
request = null;
};