做了好多个vue的项目一直使用axios用来接口请求,用法非常简单,功能很强大,不仅支持浏览器同时也支持node端,引用一下官网的介绍:
Axios 是一个基于 promise 的 HTTP 库,可以用在浏览器和 node.js 中。
从浏览器中创建 XMLHttpRequests
从 node.js 创建 http 请求
支持 Promise API
拦截请求和响应
转换请求数据和响应数据
取消请求
自动转换 JSON 数据
客户端支持防御 XSRF
Axios有默认设置,开发人员可以直接使用提供的get,post等方法也提供了用户自定义设置模式。
当做实例调用方法:
axios.get()
axios.post()
axios.delete()
axios.options()
当做构造函数使用:
axios(config)
axios({
method: 'post',
url: '/user/12345',
data: {
firstName: 'Fred',
lastName: 'Flintstone'
}
});
复制代码
自定义配置axios:
创建实例
可以使用自定义配置新建一个 axios 实例 可以根据自己项目需求配置,请求地址,请求头,超时时间等等
axios.create([config])
var instance = axios.create({
baseURL: 'https://some-domain.com/api/',
timeout: 1000,
headers: {'X-Custom-Header': 'foobar'}
});
复制代码
了解了Axios的使用方法同时支持不同的写法,那么它的内部是如何支持不同的写法呢?如何取消请求,如何进行请求拦截,和响应拦截的?接下来我们一探究竟。
源码分析
这是我项目里引入的axios的目录
adapters 请求的实现;
cancel 取消请求;
core axios主要的实现在这里 axios.js 就是入口文件;
defaults.js 默认配置;
utils.js 工具类;
先从入口文件去摸索一下;
var utils = require('./utils');
var bind = require('./helpers/bind');
var Axios = require('./core/Axios');
var defaults = require('./defaults');
function createInstance(defaultConfig) {
//这里创建了axios的实例
var context = new Axios(defaultConfig);
var instance = bind(Axios.prototype.request, context);
====================列出bind方法的实现============================
//这里用了bind方法改变了Axios.prototype.request,this的指向,返回了新的函数;
module.exports = 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);
};
};
=================bind方法的实现========================================
utils.extend(instance, Axios.prototype, context);
utils.extend(instance, context);
return instance;
}
var axios = createInstance(defaults);
axios.Axios = Axios;
axios.create = function create(instanceConfig) {
return createInstance(utils.merge(defaults, instanceConfig));
};
axios.Cancel = require('./cancel/Cancel');
axios.CancelToken = require('./cancel/CancelToken');
axios.isCancel = require('./cancel/isCancel');
axios.all = function all(promises) {
return Promise.all(promises);
};
axios.spread = require('./helpers/spread');
module.exports = axios;
module.exports.default = axios;
复制代码
这里介绍下各个引入的变量:
defaults: axios默认配置
Cancel、CancelToken 取消请求的实现
大概看完源码发现暴露出来的axios是通过createInstance()处理后的一个函数;这函数就是Axios.prototype.request;还有定义的create方法接受了一个配置对象,也是调用的createInstance(),并且使用使用了merge混合了defaults和自定义参数,axios核心就是Axios.prototype.request;
默认配置(defaults)
这里我们看一下axios的默认配置都有什么,都在defaults.js文件中 下面对主要的代码进行解释
var utils = require('./utils');
var normalizeHeaderName = require('./helpers/normalizeHeaderName');
var DEFAULT_CONTENT_TYPE = {
'Content-Type': 'application/x-www-form-urlencoded'
};
function setContentTypeIfUnset(headers, value) {
if (!utils.isUndefined(headers) && utils.isUndefined(headers['Content-Type'])) {
headers['Content-Type'] = value;
}
}
//这个方法判断了当前环境是浏览器还是node端否
//支持XMLHttpRequest 就是浏览器否则就是node
//这就是axios是如何支持浏览器和node端的原因
function getDefaultAdapter() {
var adapter;
if (typeof XMLHttpRequest !== 'undefined') {
// 对弈浏览器使用XMLHttpRequest
adapter = require('./adapters/xhr');
} else if (typeof process !== 'undefined') {
// node 使用 http
adapter = require('./adapters/http');
}
return adapter;
}
//这里就是axios的默认配置项;
var defaults = {
//最终发起请求的方法;
//下面则是对请求头,cookie 的一些配置项;
adapter: getDefaultAdapter(),
transformRequest: [function transformRequest(data, headers) {
normalizeHeaderName(headers, 'Content-Type');
if (utils.isFormData(data) ||
utils.isArrayBuffer(data) ||
utils.isBuffer(data) ||
utils.isStream(data) ||
utils.isFile(data) ||
utils.isBlob(data)
) {
return data;
}
if (utils.isArrayBufferView(data)) {
return data.buffer;
}
if (utils.isURLSearchParams(data)) {
setContentTypeIfUnset(headers, 'application/x-www-form-urlencoded;charset=utf-8');
return data.toString();
}
if (utils.isObject(data)) {
setContentTypeIfUnset(headers, 'application/json;charset=utf-8');
return JSON.stringify(data);
}
return data;
}],
transformResponse: [function transformResponse(data) {
if (typeof data === 'string') {
try {
data = JSON.parse(data);
} catch (e) { /* Ignore */ }
}
return data;
}],
timeout: 0,
xsrfCookieName: 'XSRF-TOKEN',
xsrfHeaderName: 'X-XSRF-TOKEN',
maxContentLength: -1,
validateStatus: function validateStatus(status) {
return status >= 200 && status < 300;
}
};
defaults.headers = {
common: {
'Accept': 'application/json, text/plain, */*'
}
};
//这里对常艳红方法进行设置headers
utils.forEach(['delete', 'get', 'head'], function forEachMethodNoData(method) {
defaults.headers[method] = {};
});
utils.forEach(['post', 'put', 'patch'], function forEachMethodWithData(method) {
defaults.headers[method] = utils.merge(DEFAULT_CONTENT_TYPE);
});
module.exports = defaults;
复制代码
这里对默认配置进行总结一下,defaults里面判断了当前的运行环境,支持了浏览器和node端,然后对get,post,delete方法,cookie等进行默认设置。当我们不进行自定义配置时候就是使用了这套默认的设置,当我们有自定义配置传入的时候,会使用merge方法把默认的配置替换成自定义的设置,对于默认配置文件的梳理到这里;
axios浏览器的实现(XMLHttpRequest)
从defaults中我们看到了对于浏览器的支持是基于XMLHttpRequest实现的,同时axios是基于promise实现的我们看一下,axios是如何封装XMLHttpRequest和promise的。
var utils = require('./../utils');
var settle = require('./../core/settle');
var buildURL = require('./../helpers/buildURL');
var parseHeaders = require('./../helpers/parseHeaders');
var isURLSameOrigin = require('./../helpers/isURLSameOrigin');
var createError = require('../core/createError');
module.exports = function xhrAdapter(config) {
//函数第一行就是返回了一个Promise对象
return new Promise(function dispatchXhrRequest(resolve, reject) {
var requestData = config.data;
var requestHeaders = config.headers;
if (utils.isFormData(requestData)) {
delete requestHeaders['Content-Type']; // Let the browser set it
}
//创建XMLHttpRequest实例
var request = new XMLHttpRequest();
if (config.auth) {
var username = config.auth.username || '';
var password = config.auth.password || '';
requestHeaders.Authorization = 'Basic ' + btoa(username + ':' + password);
}
//根据配置获取方法,GET,POST,请求的url,请求参数,超时时间,
request.open(config.method.toUpperCase(), buildURL(config.url, config.params, config.paramsSerializer), true);
request.timeout = config.timeout;
request.onreadystatechange = function handleLoad() {
if (!request || request.readyState !== 4) {
return;
}
if (request.status === 0 && !(request.responseURL && request.responseURL.indexOf('file:') === 0)) {
return;
}
var responseHeaders = 'getAllResponseHeaders' in request ? parseHeaders(request.getAllResponseHeaders()) : null;
var responseData = !config.responseType || config.responseType === 'text' ? request.responseText : request.response;
var response = {
data: responseData,
status: request.status,
statusText: request.statusText,
headers: responseHeaders,
config: config,
request: request
};
settle(resolve, reject, response);
request = null;
};
request.onerror = function handleError() {
reject(createError('Network Error', config, null, request));
request = null;
};
request.ontimeout = function handleTimeout() {
reject(createError('timeout of ' + config.timeout + 'ms exceeded', config, 'ECONNABORTED',
request));
request = null;
};
if (utils.isStandardBrowserEnv()) {
var cookies = require('./../helpers/cookies');
var xsrfValue = (config.withCredentials || isURLSameOrigin(config.url)) && config.xsrfCookieName ?
cookies.read(config.xsrfCookieName) :
undefined;
if (xsrfValue) {
requestHeaders[config.xsrfHeaderName] = xsrfValue;
}
}
if ('setRequestHeader' in request) {
utils.forEach(requestHeaders, function setRequestHeader(val, key) {
if (typeof requestData === 'undefined' && key.toLowerCase() === 'content-type') {
delete requestHeaders[key];
} else {
// Otherwise add header to the request
request.setRequestHeader(key, val);
}
});
}
if (config.withCredentials) {
request.withCredentials = true;
}
if (config.responseType) {
try {
request.responseType = config.responseType;
} catch (e) {
if (config.responseType !== 'json') {
throw e;
}
}
}
if (typeof config.onDownloadProgress === 'function') {
request.addEventListener('progress', config.onDownloadProgress);
}
if (typeof config.onUploadProgress === 'function' && request.upload) {
request.upload.addEventListener('progress', config.onUploadProgress);
}
//取消请求的地方
if (config.cancelToken) {
config.cancelToken.promise.then(function onCanceled(cancel) {
if (!request) {
return;
}
request.abort();
reject(cancel);
// Clean up request
request = null;
});
}
if (requestData === undefined) {
requestData = null;
}
//发送请求
request.send(requestData);
});
};
复制代码
小结:xhr.js 返回了一个promise对象,里面创建了XMLHttpRequest的链接进行默认设置,请求成功的时候,调用promise的resolve ,失败了调用reject方法。没什么特殊的难点。耐心看一下就可以看的明白。然后有一个点:config.cancelToken,这里实现了axios取消请求的方法。这里先不讨论.
Axios核心方法request
var defaults = require('./../defaults');
var utils = require('./../utils');
var InterceptorManager = require('./InterceptorManager');
var dispatchRequest = require('./dispatchRequest');
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 = utils.merge({
url: arguments[0]
}, arguments[1]);
}
config = utils.merge(defaults, {method: 'get'}, this.defaults, config);
config.method = config.method.toLowerCase();
var chain = [dispatchRequest, undefined];
var promise = Promise.resolve(config);
this.interceptors.request.forEach(function unshiftRequestInterceptors(interceptor) {
chain.unshift(interceptor.fulfilled, interceptor.rejected);
});
this.interceptors.response.forEach(function pushResponseInterceptors(interceptor) {
chain.push(interceptor.fulfilled, interceptor.rejected);
});
while (chain.length) {
promise = promise.then(chain.shift(), chain.shift());
}
return promise;
};
//这里我们看到常用的方法挂载到了原型上,
//拿我们项目里会用到
axios.get(url,{{
params:123
}})
//可以分析出来,第一个参数url,第二个是config参数然后通过merge方法处理,最后调用request
//所以我们可以知道不管我们调用get,delete,head等方法并且传入参数,最后都通过merge处理传给了request,所以
//最后执行的都是request方法;
//这就是为什么说request 方法是axios的核心方法;
utils.forEach(['delete', 'get', 'head', 'options'], function forEachMethodNoData(method) {
/*eslint func-names:0*/
Axios.prototype[method] = function(url, config) {
return this.request(utils.merge(config || {}, {
method: method,
url: url
}));
};
});
//挂载post等方法到原型上
//解析如上最后都是用的request方法;
utils.forEach(['post', 'put', 'patch'], function forEachMethodWithData(method) {
Axios.prototype[method] = function(url, data, config) {
return this.request(utils.merge(config || {}, {
method: method,
url: url,
data: data
}));
};
});
module.exports = Axios;
复制代码
小结:看了Axios.js文件,我们发现这里处理了我们常用的get,post,head等方法挂载到了原型上,当我们调用这些方法并且传入参数后,他们会通过merge方法对参数处理返回了this.request方法,所以我们最终执行的是request方法。 这也是说Axios.prototype.reques是核心方法的原因。下面我们对request进行分析一下。
request方法
//参数是merge处理后的参数
Axios.prototype.request = function request(config) {
if (typeof config === 'string') {
config = utils.merge({
url: arguments[0]
}, arguments[1]);
}
//这里对开发人员自定义配置进行处理,默认是get方法,把this.defaults覆盖到默认defaults,同时也处理了传进来的config
config = utils.merge(defaults, {method: 'get'}, this.defaults, config);
config.method = config.method.toLowerCase();
//dispatchRequest就是发送请求的方法
var chain = [dispatchRequest, undefined];
//这里定义一个成功的promist,config作为参数
var promise = Promise.resolve(config);
//当定义拦截器的时候,这里加入请求拦截和响应拦截
//当执行到这里的时候,我们配置了拦截方法,就会调动interceptors的foreach方法遍历出所有的
//拦截方法,添加到chain的前面。当有多个拦截方法的时候后添加的在前面先添加的在后面成组添加,每组两个,标识成功的回调,和失败的回调;
//chain=[funsucess1,funerr1,funsucess2,funerr2,dispatchRequest,undefined]
this.interceptors.request.forEach(function unshiftRequestInterceptors(interceptor) {
chain.unshift(interceptor.fulfilled, interceptor.rejected);
});
//相应拦截器放到了dispatchReques的后面,这样:
// [dispatchRequest,undefined,funsucess1,funerr1,funsucess2,funerr2,]
this.interceptors.response.forEach(function pushResponseInterceptors(interceptor) {
chain.push(interceptor.fulfilled, interceptor.rejected);
});
//添加完了请求拦截,相应拦截,开始执行所有的方法
while (chain.length) {
promise = promise.then(chain.shift(), chain.shift());
//这里从chain数组的前面取函数进行执行,每次取两个,正好对应拦截器添加的成功函数和失败函数。
//[funsucess1,funerr1,,dispatchRequest,undefined,funsucess2,funerr2]
//这样就保证了,请求拦截,在发送请求(dispatchRequest)前执行,响应拦截在请求后执行;
//那么axios的拦截器就是这样实现的。
//上面promise把config作为参数,所以在拦截函数里接收的就是这个config参数;
}
return promise;
};
复制代码
InterceptorManager(拦截器)
拦截器,定义了handlers 收集了所有的拦截方法。 提供了foreach,遍历所有的拦截方法; 定义了use,我们自定义拦截器的时候使用的use就是这个
================配置请求拦截的时候=================
_axios.interceptors.request.use(
function(config) {
return config;
},
function(error) {
return Promise.reject(error);
}
);
=================================
复制代码
var utils = require('./../utils');
function InterceptorManager() {
this.handlers = [];
}
//提供了use方法,
我们进行配置请求,相应拦截的时候会使用到use方法,传入了成功的回调,和事变的回调
InterceptorManager.prototype.use = function use(fulfilled, rejected) {
this.handlers.push({
fulfilled: fulfilled,
rejected: rejected
});
return this.handlers.length - 1;
};
InterceptorManager.prototype.eject = function eject(id) {
if (this.handlers[id]) {
this.handlers[id] = null;
}
};
//提供了forEach 遍历添加到handlers的拦截方法;
InterceptorManager.prototype.forEach = function forEach(fn) {
utils.forEach(this.handlers, function forEachHandler(h) {
if (h !== null) {
fn(h);
}
});
};
module.exports = InterceptorManager;
复制代码
小结,这一节我们知道了Reques方法会处理自定义的配置项,和传入的参数,默认的方法就是GET方法,知道了然后dispatchRequest是发送请求的函数,了解了axios发送请求前会有一个数组, chain=[dispatchRequest,undefined],请求拦截的函数加到请求方法dispatchRequest的的前面,响应拦截放到发送请求undefined的后面,然后顺序调用,就保证了请求拦截—>请求—>响应拦截; 接下来我们去看一下请求是如何发送的
dispatchRequest发请求
刚刚说dispatchRquest是发送请求的,这个文件暴露了dispatchRequest方法,这个方法处理的发送请求前的一些配置,headers,data,请求方法,最后返回了adapter,adapter就是xhr暴露出来的基于promise的xmlHttpReques,这里就是发送了请求,当请求成功后,promise执行resolve,把返回值传给了.then方法的onAdapterResolution(),然后进一步处理响应数据并且返回 resolve(response);如果失败了则调用onAdapterRejection()处理失败后的响应数据返回了Promise.reject,所以最后返回的都是Promise的resolve,reject;
'use strict';
var utils = require('./../utils');
var transformData = require('./transformData');
var isCancel = require('../cancel/isCancel');
var defaults = require('../defaults');
var isAbsoluteURL = require('./../helpers/isAbsoluteURL');
var combineURLs = require('./../helpers/combineURLs');
function throwIfCancellationRequested(config) {
if (config.cancelToken) {
config.cancelToken.throwIfRequested();
}
}
module.exports = function dispatchRequest(config) {
throwIfCancellationRequested(config);
if (config.baseURL && !isAbsoluteURL(config.url)) {
config.url = combineURLs(config.baseURL, config.url);
}
config.headers = config.headers || {};
config.data = transformData(
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;
//返回基于Promise的xhr
return adapter(config).then(function onAdapterResolution(response) {
//成功的回调,处理响应数据;
throwIfCancellationRequested(config);
response.data = transformData(
response.data,
response.headers,
config.transformResponse
);
return response;
}, function onAdapterRejection(reason) {
//失败的回调并且返回return Promise.reject
if (!isCancel(reason)) {
throwIfCancellationRequested(config);
if (reason && reason.response) {
reason.response.data = transformData(
reason.response.data,
reason.response.headers,
config.transformResponse
);
}
}
return Promise.reject(reason);
});
};
复制代码
我们知道了axios是如何发送请求的了。成功的时候返回了成功的数据 resolve(response);,失败了返回Promise.reject,然后我们再回到 Axios.js的 Axios.prototype.request方法中
Axios.prototype.request = function request(config) {
//删除了无关的======================================
var chain = [dispatchRequest, undefined];
var promise = Promise.resolve(config);
//删除了无关的======================================
n
while (chain.length) {
那么这里执行完了dispatchRequest 这个promise得到dispatchRequest返回的Promise,resolve(response)或者reject,
promise = promise.then(chain.shift(), chain.shift());
}
//这里最后返回了这个promise,那么我们在调用的时候就会得到最后请求返回的数据
return promise;
};
项目代码================================如下
axios.get(url)这个就是返回的promise
那么调用axios.get(url).then(res=>{
这个res就是上面返回的 resolve(response)
})
catch则是失败的返回
复制代码
小结:到这里我们知道了整个axios发送请求,获取响应,应用promise进行链式调用,那么如何取消请求呢?
取消请求
引用一个例子,创建请求的时候传入了CancelToken,然后调用这个c参数,调用之后就可以取消了,我们去创建请求的时候传入了CancelToken看一看究竟。
var CancelToken = axios.CancelToken;
var cancel;
axios.get('/user/12345', {
cancelToken: new CancelToken(function executor(c) {
// executor 函数接收一个 cancel 函数作为参数
cancel = c;
})
});
// 取消请求
cancel();
复制代码
我们看一下 CancelToken.js的主要代码。 当我们new一个CancelToken的时候,设置了promise,默认是resolve成功的回调。 调用executro把cancel方法暴露出去,当再外部调用cancel的时候,this.promise的状态变成了resole
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) {
// Cancellation has already been requested
return;
}
token.reason = new Cancel(message);
resolvePromise(token.reason);
});
}
复制代码
在xhr.js中发现了这样几行代码
//使用了cancelToken
if (config.cancelToken) {
//这里当promise变成成功后,调用了then执行request.abort()这样就取消了这个请求;
config.cancelToken.promise.then(function onCanceled(cancel) {
if (!request) {
return;
}
//取消请求
request.abort();
reject(cancel);
// Clean up request
request = null;
});
}
复制代码
总结
到这里对于axios的大概就了解的差不多了,axios支持自定义配置,支持get等api,其实最终都是调用原型上的Axios.prototype.request方法,这个方法会对传入的参数进行混合。 axios对于浏览器基于Promise创建了XmlHttpReques对象,成功失败返回的是Promise,所以我们可以调用.then方法接收返回值。
对于请求拦截响应拦截,当使用拦截的.use的时候传入了成功和失败的回调方法,在axios调用request放发的时候,会创建个数组,chain=[dispatchRequest,undefined] 把请求拦截放在dispatchRequest前面,响应拦截放在undifined后面,然后按照顺序循环调用每次取出两个,对应传入的成功回调,和失败回调,这个数组保证了,请求拦截和响应拦截的顺序。
对于取消请求在创建axios的时候传入了取消的对象cancelToken,这个对象创建了一个promise并向外暴露了一个取消方法,当调用这个取消方法的时候,会把这个promise状态设置为成功,当成功的时候,会在xhr文件里调用 取消请求的 request.abort()方法。
作者:Wang富贵儿
链接:https://juejin.cn/post/6844904041009381383
来源:稀土掘金
著作权归作者所有。商业转载请联系作者获得授权,非商业转载请注明出处。