工具类
bind
1 2 3 4 5 6 7 8 9
| function bind(fn, thisArg) { return function wrap() { var args = new Array(arguments.length); for (var i = 0; i < args.length; i++) { args[i] = arguments[i]; } return fn.apply(thisArg, args); }; };
|
extend
1 2 3 4 5 6 7 8 9 10
| function extend(a, b, thisArg) { forEach(b, function assignValue(val, key) { if (thisArg && typeof val === 'function') { a[key] = bind(val, thisArg); } else { a[key] = val; } }); return a; }
|
merge
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19
| function merge() { var result = {}; function assignValue(val, key) { if (isPlainObject(result[key]) && isPlainObject(val)) { result[key] = merge(result[key], val); } else if (isPlainObject(val)) { result[key] = merge({}, val); } else if (isArray(val)) { result[key] = val.slice(); } else { result[key] = val; } }
for (var i = 0, l = arguments.length; i < l; i++) { forEach(arguments[i], assignValue); } return result; }
|
从使用方式看起
对几种调用方式做了编号
axios(option)
axios(url[, option])
axios[method](url[, option])
(get、delete
等方法)
axios[method](url[, data[, option]])
(post、put
等方法)
axios.request(option)
让我们看看源码他是怎么做到能够支持这么多调用方式的
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23
|
function createInstance(defaultConfig) { var context = new Axios(defaultConfig);
var instance = bind(Axios.prototype.request, context);
utils.extend(instance, Axios.prototype, context);
utils.extend(instance, context);
return instance; }
var axios = createInstance(defaults);
|
主要是为了能够做到1、2种调用方式,所以是返回request函数,并在其上挂载Axios实例的方法
接下来看看上述代码操作的主体:Axios、Axios.prototype.request 以及 Axios.prototype上的请求方法
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38
| function Axios(instanceConfig) { this.defaults = instanceConfig; this.interceptors = { request: new InterceptorManager(), response: new InterceptorManager() }; } Axios.prototype.request = function request(config) { if (typeof config === 'string') { config = arguments[1] || {}; config.url = arguments[0]; } else { config = config || {}; } };
utils.forEach(['delete', 'get', 'head', 'options'], function forEachMethodNoData(method) { Axios.prototype[method] = function(url, config) { return this.request(mergeConfig(config || {}, { method: method, url: url, data: (config || {}).data })); }; });
utils.forEach(['post', 'put', 'patch'], function forEachMethodWithData(method) { Axios.prototype[method] = function(url, data, config) { return this.request(mergeConfig(config || {}, { method: method, url: url, data: data })); }; });
|
结合上面createInstance
的代码,我们可以看到为什么我们可以使用多种方式调用axios
我们先不看具体请求逻辑是怎么实现的,先看看拦截器是怎么做到在请求前后预处理的。
拦截器(interceptor)
在上文Axios
的构造器中我们可以看到
1 2 3 4
| this.interceptors = { request: new InterceptorManager(), response: new InterceptorManager() };
|
InterceptorManager
类用以管理拦截器
InterceptorManager类
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30
| function InterceptorManager() { this.handlers = []; }
InterceptorManager.prototype.use = function use(fulfilled, rejected, options) { this.handlers.push({ fulfilled: fulfilled, rejected: rejected, synchronous: options ? options.synchronous : false, runWhen: options ? options.runWhen : null }); return this.handlers.length - 1; };
InterceptorManager.prototype.eject = function eject(id) { if (this.handlers[id]) { this.handlers[id] = null; } };
InterceptorManager.prototype.forEach = function forEach(fn) { utils.forEach(this.handlers, function forEachHandler(h) { if (h !== null) { fn(h); } }); };
|
request中InterceptorManager使用方式
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42
| Axios.prototype.request = function request(config) {
var requestInterceptorChain = []; this.interceptors.request.forEach(function unshiftRequestInterceptors(interceptor) { if (typeof interceptor.runWhen === 'function' && interceptor.runWhen(config) === false) { return; }
requestInterceptorChain.unshift(interceptor.fulfilled, interceptor.rejected); });
var responseInterceptorChain = []; this.interceptors.response.forEach(function pushResponseInterceptors(interceptor) { responseInterceptorChain.push(interceptor.fulfilled, interceptor.rejected); });
var promise;
var chain = [dispatchRequest, undefined];
Array.prototype.unshift.apply(chain, requestInterceptorChain); chain.concat(responseInterceptorChain);
promise = Promise.resolve(config); while (chain.length) { promise = promise.then(chain.shift(), chain.shift()); }
return promise; }
|
大致流程就是如此
synchronous
但是axios还为请求拦截器提供了synchronous
选项,需要全部请求拦截器设置synchronous
为true,那么就以同步的方式运行
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62
| Axios.prototype.request = function request(config) { var requestInterceptorChain = []; var synchronousRequestInterceptors = true; this.interceptors.request.forEach(function unshiftRequestInterceptors(interceptor) { if (typeof interceptor.runWhen === 'function' && interceptor.runWhen(config) === false) { return; } synchronousRequestInterceptors = synchronousRequestInterceptors && interceptor.synchronous;
requestInterceptorChain.unshift(interceptor.fulfilled, interceptor.rejected); });
var responseInterceptorChain = []; this.interceptors.response.forEach(function pushResponseInterceptors(interceptor) { responseInterceptorChain.push(interceptor.fulfilled, interceptor.rejected); });
var promise; if (!synchronousRequestInterceptors) { var chain = [dispatchRequest, undefined];
Array.prototype.unshift.apply(chain, requestInterceptorChain); chain.concat(responseInterceptorChain);
promise = Promise.resolve(config); while (chain.length) { promise = promise.then(chain.shift(), chain.shift()); }
return promise; }
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); }
while (responseInterceptorChain.length) { promise = promise.then(responseInterceptorChain.shift(), responseInterceptorChain.shift()); }
return promise; };
|
启发:链式调用
1 2 3 4
| new Man("Hank").sleep(1).eat("supper").sleep(1).eat("me").sleepFirst(2);
|
写法放在文末
发起请求dispatchRequest
在拿到请求拦截器处理后的config后
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48
| function dispatchRequest(config) { throwIfCancellationRequested(config);
config.headers = config.headers || {};
config.data = transformData.call( config, config.data, config.headers, config.transformRequest );
config.headers = utils.merge( config.headers.common || {}, config.headers[config.method] || {}, config.headers );
utils.forEach( ['delete', 'get', 'head', 'post', 'put', 'patch', 'common'], function cleanHeaderConfig(method) { delete config.headers[method]; } );
var adapter = config.adapter || defaults.adapter;
return adapter(config).then(function onAdapterResolution(response) { return response; }, function onAdapterRejection(reason) { return Promise.reject(reason); }); };
function throwIfCancellationRequested(config) { if (config.cancelToken) { config.cancelToken.throwIfRequested(); } }
|
跨端实现(adapter)
适配器模式
- 是否有
XMLHttpRequest
对象来判断是否是web环境
- 是否有
process
对象来判断node环境
1 2 3 4 5 6 7 8 9 10
| function getDefaultAdapter() { var adapter; if (typeof XMLHttpRequest !== 'undefined') { adapter = require('./adapters/xhr'); } else if (typeof process !== 'undefined' && Object.prototype.toString.call(process) === '[object process]') { adapter = require('./adapters/http'); } return adapter; }
|
封装xhr
返回一个promise
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30
| function xhrAdapter(config) { return new Promise(function dispatchXhrRequest(resolve, reject) { var request = new XMLHttpRequest(); var fullPath = buildFullPath(config.baseURL, config.url); request.open(config.method.toUpperCase(), buildURL(fullPath, config.params, config.paramsSerializer), true); request.timeout = config.timeout; request.onloadend = function onloadend() { settle(resolve, reject, response); request = null; }; request.onabort = function handleError() { reject(); request = null; }; request.onerror = function handleError() { reject(); request = null; }; request.ontimeout = function handleTimeout() { reject(); request = null; }; request.send(requestData); }); };
|
验证服务端的返回结果是否通过验证:
1 2 3 4 5 6 7 8 9
| function settle(resolve, reject, response) { var validateStatus = response.config.validateStatus; if (!response.status || !validateStatus || validateStatus(response.status)) { resolve(response); } else { reject(); } };
|
同理将http封装成promise
取消请求(cancelToken)
如何使用
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19
| import axios from 'axios'
axios.get(url, { cancelToken: new axios.CancelToken(cancel => { if () { cancel('取消日志'); } }) });
const CancelToken = axios.CancelToken; const source = CancelToken.source(); axios.get(url, { cancelToken: source.token }); source.cancel('取消日志');
|
源码
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44
| function CancelToken(executor) { if (typeof executor !== 'function') { throw new TypeError('executor must be a function.'); }
var resolvePromise; this.promise = new Promise(function promiseExecutor(resolve) { resolvePromise = resolve; });
var token = this; executor(function cancel(message) { if (token.reason) { return; }
token.reason = new Cancel(message); resolvePromise(token.reason); }); }
CancelToken.source = function source() { var cancel; var token = new CancelToken(function executor(c) { cancel = c; }); return { token: token, cancel: cancel }; };
if (config.cancelToken) { config.cancelToken.promise.then(function onCanceled(cancel) { if (!request) { return; } request.abort(); reject(cancel); request = null; }); }
|
启发
链式调用
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50
| class Man { constructor (name) { this.name = name; this.arr = []; console.log(`This is ${name}`); const executor = i => { if (i < this.arr.length) { this.arr[i]().then(value => { console.log(value); executor(i + 1); }); } } Promise.resolve().then(()=> { executor(0) }, 0); } sleep(sec) { this.arr.push(() => { return new Promise(resolve => { setTimeout(() => { resolve(`Wale up after ${sec}`); }, sec * 1000); }) }) return this; } eat(food) { this.arr.push(() => { return Promise.resolve(`Eat ${food}`); }) return this; } sleepFirst(sec) { this.arr.unshift(() => { return new Promise(resolve => { setTimeout(() => { resolve(`Wale up after ${sec}`); }, sec * 1000); }) }) return this; } }
|
参考
Axios源码深度剖析