Axios 是一个基于 promise 的网络请求库,可以用于浏览器和 node.js。懂的都懂!
本文将从接下来的几个点,分析 axios:
在axios.js文件中会初始调用一次createInstance函数。在此函数中会new Axios创建一个名为context的实例和使用bind函数对Axios.prototype.request进行包裹并返回名为instance的函数。
然后,将Axios.prototype和context的属性和方法extend到instance函数。最后,在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);
通过上述步骤后,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);...
}
注意通常我们在使用 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)));
// 错误用法
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)));
请求拦截器在我们日常开发中经常会用到。比如说我们在发送请求前记录发送时间(记录接口响应所花的时间),或者添加业务 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);
});
源码 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;
}
通过 promise 的链式调用来实现异步请求拦截器。生成的 promise 链如下:
var promise = Promise.resolve(config);
promise.then("请求拦截器函数2", "请求拦截器异常处理函数2").then("请求拦截器函数1", "请求拦截器异常处理函数1")... // 可能有多个请求拦截器,也可能没有.then(dispatchRequest, undefined).then("请求成功处理函数", "请求失败处理函数")
但是这样的方式也带了问题:
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--;
}
源码 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;
同步拦截器就比较简单了。同时也没上面异步拦截器的问题。
示例代码
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--;
}
源码 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);});
};
在这个函数中通过适配器模式确定发送请求的模块,然后发出请求。
源码 lib/defaults/index.js
function getDefaultAdapter() {var adapter;if (typeof XMLHttpRequest
}
通过适配器模式,识别不同的环境,兼容多种格式。对外提供统一的接口。
源码 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;
}
响应拦截器也是通过 promise 的链式调用来实现的。生成的 promise 链如下:
var promise = Promise.resolve(config);
promise.then(dispatchRequest, undefined).then("响应拦截器1", "响应拦截器异常处理函数1").then("响应拦截器2", "响应拦截器异常处理函数2").then("请求成功处理函数", "请求失败处理函数")
axios 支持两种取消请求的方式。CancelToken 和 AbortController。在 v0.22.0 后开始,不推荐使用 CancelToken。
源码 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);
}
示例代码
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.通过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 请求。
源码 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);}
}
示例代码
const controller = new AbortController();
axios.get('/foo/bar', {signal: controller.signal
}).then(function(response) {//...
});
// 取消请求
controller.abort()
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 请求。
目前只看了这么多,以后有时间再完善!