根据package.json
配置中的main
主入口,可以看到入口文件的是index.js
index.js
module.exports = require('./lib/axios');
进入入口文件,可以看出axios
的内部逻辑均在lib
文件夹下。
lib/axios.js
生成axios
实例对象。
function createInstance(defaultConfig) {
// 创建一个实例,用于指定一个上下文
var context = new Axios(defaultConfig);
// 将原型中的request方法的this指向当前实例形成一个新的实例,把该方法作为实例方法使用。
var instance = bind(Axios.prototype.request, context);
// 将构造函数 Axios.prototype 上的方法挂载到新的实例 instance 上,然后将原型各个方法中的 this 指向 context,开发中才能使用 axios.get/post… 等等
utils.extend(instance, Axios.prototype, context);
// 将构造函数 Axios 的实例属性挂载到新的实例 instance 上
utils.extend(instance, context);
// 暴露一个创建实例的方法
instance.create = function create(instanceConfig) {
return createInstance(mergeConfig(defaultConfig, instanceConfig));
};
return instance;
}
// 生成供使用的默认实例, 这个default就是默认配置。
var axios = createInstance(defaults);
在createInstance
方法,可以发现里边并不是简单的创建一个实例,而是通过改变上下文及挂载属性的方式从而实现支持axios()
、axios.get()
等方式。
同时还暴露出构造函数Axios
及用于取消请求的CancelToken
等
axios.Axios = Axios;
axios.Cancel = require('./cancel/Cancel');
axios.CancelToken = require('./cancel/CancelToken');
axios.isCancel = require('./cancel/isCancel');
扩展
如果对
createInstance
方法还不是非常理解的话,我们写个简单的demo
来更深入的了解一下。已经理解了的同学们可以跳过这部分。首先是
bind
方法,其实就是axios
里内部实现的一个Object.bind
方法。将Axios.prototype.request
这个原型方法的this
指向刚刚创建的一个axios
实例,并返回一个函数,用于接收参数传递给Axios.prototype.request
方法。这样就可以使用axios({method: 'post',url: '',data: {}})
这种方式。
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);
};
}
var instance = bind(Axios.prototype.request, context);
// 也可以改写成
var instance = Axios.prototype.request.bind(context);
extend
将构造函数Axios.prototype
上的方法挂载到新的实例instance
上,然后将原型各个方法中的this
指向context
function extend(a, b, thisArg) {
forEach(b, function assignValue(val, key) {
if (thisArg && typeof val === 'function') {
// 将原型各个方法中的 this 指向 context
a[key] = bind(val, thisArg);
} else {
a[key] = val;
}
});
return a;
}
function forEach(obj, fn) {
if (obj === null || typeof obj === 'undefined') {
return;
}
if (typeof obj !== 'object') {
obj = [obj];
}
if (Object.prototype.toString.call(obj) === '[object Array]') {
for (var i = 0, l = obj.length; i < l; i++) {
fn.call(null, obj[i], i, obj);
}
} else {
for (var key in obj) {
if (Object.prototype.hasOwnProperty.call(obj, key)) {
fn.call(null, obj[key], key, obj);
}
}
}
}
通过查看
extend
源码我们可以很容易理解createInstance
方法中的两行扩展代码。
// 将构造函数 Axios.prototype 上的方法挂载到新的实例 instance 上,然后将原型各个方法中的 this 指向 context,开发中才能使用 axios.get/post… 等等
utils.extend(instance, Axios.prototype, context);
// 将构造函数 Axios 的实例属性挂载到新的实例 instance 上
utils.extend(instance, context);
来写一个
demo
class Base {
constructor(name) {
this.name = name;
}
}
Base.prototype.say = function say(message) {
console.log(message);
return this;
};
Base.prototype.location = 'china';
let b = new Base('_base');
b._inner = '_inner attribute';
// 将say方法指向b实例,这样就可以通过instance()方式将参数传递给say方法。
let instance = Base.prototype.say.bind(b);
console.log(Object.getOwnPropertyNames(instance)); // ['length', 'name']
console.log(instance('say this message')); // say this message
// 将Base中的原型方法添加到instance中,此时实例b中的_inner属性并没有添加到instance上
extend(instance, Base.prototype, b);
console.log(Object.getOwnPropertyNames(instance)); // ['length', 'name', 'say', 'location']
// 将实例b中的所有属性添加到instance上
extend(instance, b);
console.log(Object.getOwnPropertyNames(instance)); // ['length', 'name', 'say', 'location', '_inner']
在前面讲解createInstance
方法时,可以看到传了一个默认配置,这个是axios
提供的内置属性和方法,可以被覆盖。
var defaults = {
transitional: {
silentJSONParsing: true,
forcedJSONParsing: true,
clarifyTimeoutError: false,
},
// 请求适配器
adapter: getDefaultAdapter(),
// 请求转换器
transformRequest: [],
// 响应转换器
transformResponse: [],
timeout: 0,
xsrfCookieName: 'XSRF-TOKEN',
xsrfHeaderName: 'X-XSRF-TOKEN',
maxContentLength: -1,
maxBodyLength: -1,
validateStatus: function validateStatus(status) {
return status >= 200 && status < 300;
},
headers: {
common: {
Accept: 'application/json, text/plain, */*',
},
},
};
xsrfCookieName
、xsrfHeaderName
用于防止 csrf
攻击
if (utils.isStandardBrowserEnv()) {
// Add xsrf header
var xsrfValue =
(config.withCredentials || isURLSameOrigin(fullPath)) &&
config.xsrfCookieName
? cookies.read(config.xsrfCookieName)
: undefined;
if (xsrfValue) {
requestHeaders[config.xsrfHeaderName] = xsrfValue;
}
}
扩展
CSRF攻击攻击原理及过程如下:
用户C打开浏览器,访问受信任网站A,输入用户名和密码请求登录网站A;
在用户信息通过验证后,网站A产生Cookie信息并返回给浏览器,此时用户登录网站A成功,可以正常发送请求到网站A;
用户未退出网站A之前,在同一浏览器中,打开一个TAB页访问网站B;
网站B接收到用户请求后,返回一些攻击性代码,并发出一个请求要求访问第三方站点A;
浏览器在接收到这些攻击性代码后,根据网站B的请求,在用户不知情的情况下携带Cookie信息,向网站A发出请求。网站A并不知道该请求其实是由B发起的,所以会根据用户C的Cookie信息以C的权限处理该请求,导致来自网站B的恶意代码被执行。
现在网站 B 发起的请求里虽然有 cookie 但是并不包含前端和后端约定好的 header 中的 X-XSRF-TOKEN 字段,所以请求会失败,这样便防止了 csrf 攻击。
Axios
构造函数function Axios(instanceConfig) {
// 默认配置
this.defaults = instanceConfig;
// 拦截器
this.interceptors = {
request: new InterceptorManager(),
response: new InterceptorManager(),
};
}
在request
这个原型方式,可以通过传入config
来覆盖默认的配置。
request
方法具体做了以下工作:
promise
的链式调用,处理请求、响应拦截器以及发送请求等操作。Axios.prototype.request = function request(config) {
// 兼容多种传参方式
// 1. request('example/url', { method: 'post' })
// 2. request({ url: 'example/url', method: 'post' });
if (typeof config === 'string') {
config = arguments[1] || {};
config.url = arguments[0];
} else {
config = config || {};
}
// 合并配置
config = mergeConfig(this.defaults, config);
// 设置方法,默认get
...
// 请求拦截器
var requestInterceptorChain = [];
// 拦截器是否是同步的
var synchronousRequestInterceptors = true;
// 拦截器处理,先跳过,下一部分在详解
...
// 发送请求
try {
promise = dispatchRequest(newConfig);
} catch (error) {
return Promise.reject(error);
}
// 执行response拦截器
while (responseInterceptorChain.length) {
promise = promise.then(
responseInterceptorChain.shift(),
responseInterceptorChain.shift()
);
}
return promise;
};
mergeConfig
方法对不同的参数进行是否选用默认参数的处理,比如url
、method
、data
等参数就没有默认的。
var mergeMap = {
// 如果用户配置中不存在,不会选用默认
url: valueFromConfig2,
method: valueFromConfig2,
data: valueFromConfig2,
// 用户配置中不存在,会选用默认
baseURL: defaultToConfig2,
transformRequest: defaultToConfig2,
transformResponse: defaultToConfig2,
paramsSerializer: defaultToConfig2,
timeout: defaultToConfig2,
timeoutMessage: defaultToConfig2,
withCredentials: defaultToConfig2,
adapter: defaultToConfig2,
responseType: defaultToConfig2,
xsrfCookieName: defaultToConfig2,
xsrfHeaderName: defaultToConfig2,
onUploadProgress: defaultToConfig2,
onDownloadProgress: defaultToConfig2,
decompress: defaultToConfig2,
maxContentLength: defaultToConfig2,
maxBodyLength: defaultToConfig2,
transport: defaultToConfig2,
httpAgent: defaultToConfig2,
httpsAgent: defaultToConfig2,
cancelToken: defaultToConfig2,
socketPath: defaultToConfig2,
responseEncoding: defaultToConfig2,
validateStatus: mergeDirectKeys,
};
来看看axios
是如何通过promise
实现拦截器的。
拦截器管理机制其实很简单。就只有一个属性(用于保存拦截器)及三个原型方法(添加、移除、执行)。
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);
}
});
};
在Axios
构造函数里创建了两个拦截器管理实例
this.interceptors = {
request: new InterceptorManager(),
response: new InterceptorManager(),
};
在axios
实例创建后,可通过 use
方法注册成功和失败的钩子函数,比如 axios.interceptors.request.use((config) => config, (error) => error, options);
// 添加请求拦截器
axios.interceptors.request.use(
function (config) {
// 在发送请求之前做些什么
return config;
},
function (error) {
// 对请求错误做些什么
return Promise.reject(error);
}
);
// 添加响应拦截器
axios.interceptors.response.use(
function (response) {
// 对响应数据做点什么
return response;
},
function (error) {
// 对响应错误做点什么
return Promise.reject(error);
}
);
需要主要的是,在传递use
方法的第一个参数时必须返回config
,保证下一个promise
能获取到处理后的参数。 options
是可选参数对象,可传入两个属性(synchronous
, runWhen
);
synchronous
: 在添加一个请求拦截器时,axios
默认这些是异步的,如果拦截器是同步的,不想被默认成异步造成延迟,可以往options
中传入该属性。
axios.interceptors.request.use(function (config) {
return config;
}, null, { synchronous: true });
runWhen
: 用于运行时检查执行特定的拦截器,可以添加一个runWhen
函数到options
,当函数返回false
时该拦截器不会执行。
function onGetCall(config) {
return config.method === 'get';
}
axios.interceptors.request.use(function (config) {
return config;
}, null, { runWhen: onGetCall });
request
原型方法中对这些拦截器的操作其实也很简单,就是创建一个栈,将use
方法获取的所有处理函数都推进栈中。
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;
// 将处理方法推进栈中,采用unshift方法
requestInterceptorChain.unshift(
interceptor.fulfilled,
interceptor.rejected
);
});
// 响应拦截器栈
var responseInterceptorChain = [];
// 循环将响应拦截器加入拦截链中
this.interceptors.response.forEach(function pushResponseInterceptors(
interceptor
) {
responseInterceptorChain.push(interceptor.fulfilled, interceptor.rejected);
});
...
}
上面的代码将用户设置的请求跟响应拦截器通过unshift
和push
方法分别放进了两个栈中,下面继续看看axios
是如何将所有的拦截器跟发送请求放进一个promise
链中。
异步执行
Axios.prototype.request = function request(config) {
...
var promise;
// 异步执行方法
if (!synchronousRequestInterceptors) {
// 整一个执行链条
var chain = [dispatchRequest, undefined];
// 头unshift进request拦截器
Array.prototype.unshift.apply(chain, requestInterceptorChain);
// 尾push进response拦截器
chain = chain.concat(responseInterceptorChain);
promise = Promise.resolve(config);
while (chain.length) {
promise = promise.then(chain.shift(), chain.shift());
}
return promise;
}
// 同步执行方法
...
}
axios
采用promise.resolve
的方式将拦截器异步化。将所有请求拦截器放在请求方法之前,所有的响应拦截器放在后。遍历所有的方法通过promise
的then
方法将所有方法放在一条链上。
promise.resolve
异步化简单示例。
function printA(data) {
console.log(data);
return data + 'a';
}
function asyncPrintA() {
let promise = Promise.resolve('async a')
.then(printA)
.then(printA)
.then(printA);
return promise;
}
console.log(asyncPrintA());
printA('a');
console.log('log after a, before async a');
// a
// log after a, before async a
// async a
// async aa
// async aaa
来看同步执行的方法。
Axios.prototype.request = function request(config) {
...
// 同步执行方法
var newConfig = config;
// 遍历执行request拦截器
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);
}
// 执行response拦截器
while (responseInterceptorChain.length) {
promise = promise.then(
responseInterceptorChain.shift(),
responseInterceptorChain.shift()
);
}
return promise;
}
请求拦截器是在请求方法方法之前执行,因此不需要考虑请求响应的情况,直接遍历执行所有的方法就可以了,当其中一个拦截器出现错误时会中断整个请求。当执行完后发送请求,由于请求是异步的,因此响应拦截器也必须是异步的,所以通过promise
的then
方法串起来。
dispatchRequest
在发送请求前,会进行一系列的操作:
throwIfCancellationRequested(config);
这个方法会判断请求体内的cancel
是否已经执行了,如果执行了就会直接抛出原因,不会发送请求。
data
,比如对 post
请求的 data
进行字符串化 JSON.stringify(data)
config.data = transformData.call(
config,
config.data,
config.headers,
config.transformRequest
);
通常来说,我们不需要传入一个transformRequest
参数,直接使用默认的就可以。
transformRequest
transformRequest: [
function transformRequest(data, headers) {
normalizeHeaderName(headers, 'Accept');
normalizeHeaderName(headers, 'Content-Type');
// 满足的data类型
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) ||
(headers && headers['Content-Type'] === 'application/json')
) {
setContentTypeIfUnset(headers, 'application/json');
return stringifySafely(data);
}
return data;
},
],
如果你要自己写一个转换方法的,需要注意这个方法只满足PUT
, POST
, PATCH
和DELETE
这些方法,而且由于传入的是一个数组,最后一个方法必须返回string
,buffer
,ArrayBuffer
,FormData
,Stream
类型的数据。
xhr
和 node
端的 http
)var adapter = config.adapter || defaults.adapter;
同样,适配器也可以自定义。
我们看看默认的适配器
function getDefaultAdapter() {
var adapter;
if (typeof XMLHttpRequest !== 'undefined') {
// For browsers use XHR adapter
adapter = require('./adapters/xhr');
} else if (
typeof process !== 'undefined' &&
Object.prototype.toString.call(process) === '[object process]'
) {
// For node use HTTP adapter
adapter = require('./adapters/http');
}
return adapter;
}
逻辑很简单,就是判断XMLHttpRequest
这个对象存不存在,存在说明是出于浏览器环境,否则便是node
环境。
xhrAdapter
现在来看一下xhrAdapter
function xhrAdapter(config) {
return new Promise(function dispatchXhrRequest(resolve, reject) {
var requestData = config.data;
var requestHeaders = config.headers;
var responseType = config.responseType;
var onCanceled;
// 请求完成了,取消cancel请求监听
function done() {
if (config.cancelToken) {
config.cancelToken.unsubscribe(onCanceled);
}
if (config.signal) {
config.signal.removeEventListener('abort', onCanceled);
}
}
// 起一个xml请求
var request = new XMLHttpRequest();
// http 基础鉴权
if (config.auth) {
var username = config.auth.username || '';
var password = config.auth.password
? unescape(encodeURIComponent(config.auth.password))
: '';
requestHeaders.Authorization = 'Basic ' + btoa(username + ':' + password);
}
// 获取完整请求路径
var fullPath = buildFullPath(config.baseURL, config.url);
// 建立连接
request.open(
config.method.toUpperCase(),
buildURL(fullPath, config.params, config.paramsSerializer),
true
);
// 设置过期时间
request.timeout = config.timeout;
// 请求完成
function onloadend() {
if (!request) {
return;
}
var responseHeaders =
'getAllResponseHeaders' in request
? parseHeaders(request.getAllResponseHeaders())
: null;
var responseData =
!responseType || responseType === 'text' || responseType === 'json'
? request.responseText
: request.response;
var response = {
data: responseData,
status: request.status,
statusText: request.statusText,
headers: responseHeaders,
config: config,
request: request,
};
// 根据response结果 resolve 或者 reject 一个 promise
settle(
function _resolve(value) {
resolve(value);
done();
},
function _reject(err) {
reject(err);
done();
},
response
);
request = null;
}
// 添加onloadend事件
if ('onloadend' in request) {
request.onloadend = onloadend;
} else {
// 通过readyState来模拟实现一个onloaded事件
request.onreadystatechange = function handleLoad() {
if (!request || request.readyState !== 4) {
return;
}
if (
request.status === 0 &&
!(request.responseURL && request.responseURL.indexOf('file:') === 0)
) {
return;
}
setTimeout(onloadend);
};
}
request.onabort = function handleAbort() {
if (!request) {
return;
}
reject(createError('Request aborted', config, 'ECONNABORTED', request));
request = null;
};
request.onerror = function handleError() {
reject(createError('Network Error', config, null, request));
request = null;
};
// 超时
request.ontimeout = function handleTimeout() {
var timeoutErrorMessage = config.timeout
? 'timeout of ' + config.timeout + 'ms exceeded'
: 'timeout exceeded';
var transitional = config.transitional || defaults.transitional;
if (config.timeoutErrorMessage) {
timeoutErrorMessage = config.timeoutErrorMessage;
}
reject(
createError(
timeoutErrorMessage,
config,
transitional.clarifyTimeoutError ? 'ETIMEDOUT' : 'ECONNABORTED',
request
)
);
request = null;
};
// xsrf相关
...
// 把用户设置的请求头添加到request中
if ('setRequestHeader' in request) {
utils.forEach(requestHeaders, function setRequestHeader(val, key) {
if (
typeof requestData === 'undefined' &&
key.toLowerCase() === 'content-type'
) {
// 当data为undefined时,移除头部
delete requestHeaders[key];
} else {
request.setRequestHeader(key, val);
}
});
}
// 响应数据类型
if (responseType && responseType !== 'json') {
request.responseType = config.responseType;
}
// 下载进度监听
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.signal) {
// Handle cancellation
// eslint-disable-next-line func-names
onCanceled = function (cancel) {
if (!request) {
return;
}
reject(
!cancel || (cancel && cancel.type) ? new Cancel('canceled') : cancel
);
// 取消请求
request.abort();
request = null;
};
config.cancelToken && config.cancelToken.subscribe(onCanceled);
if (config.signal) {
config.signal.aborted
? onCanceled()
: config.signal.addEventListener('abort', onCanceled);
}
}
if (!requestData) {
requestData = null;
}
// 发送请求
request.send(requestData);
});
};
整一个xhrAdapter
适配器的原理非常简单,就是实例化一个XMLHttpRequest
,将config
中配置的多种参数添加到该实例上,同时对实例的多种事件进行监听。判断用户是否传入cancelToken
,添加cancel
回调,就是取消发送该请求。
cancelToken
来看一下cancelToken
如何使用以及axios
如何实现取消请求。
const cancelToken = axios.CancelToken;
const source = cancelToken.source();
axios
.get("http://localhost:3000/", {
cancelToken: source.token,
})
.then((res) => {
console.log(res);
})
.catch((err) => {
console.log(err);
});
source.cancel("cancel by the user");
source()
方法返回了一个包含了CancelToken
的实例跟一个用于取消请求的函数。
CancelToken.source = function source() {
var cancel;
var token = new CancelToken(function executor(c) {
cancel = c;
});
return {
token: token,
cancel: cancel,
};
};
cancel
方法接收构造函数 CancelToken
内部的一个 cancel
函数,用于取消请求。
function CancelToken(executor) {
// ...
var token = this;
// executor里边的参数函数就是cancel
executor(function cancel(message) {
if (token.reason) {
return;
}
token.reason = new Cancel(message);
resolvePromise(token.reason);
});
}
当用户在取消请求执行source.cancel(message)
的时候,会将CancelToken
内部的promise
状态变成resolve
,这样就会调用promise
的then
方法,触发所有收集到的监听器(监听器是在dispatchRequest
方法中加入的onCanceled
,用于取消请求)。
function CancelToken(executor) {
var resolvePromise;
// 创建了一个promise 拿到这个resolve
this.promise = new Promise(function promiseExecutor(resolve) {
// 设置的cancel message作为返回值
resolvePromise = resolve;
});
var token = this;
// 调用promise的then方法
this.promise.then(function (cancel) {
// 触发收集的监听器
if (!token._listeners) return;
var i;
var l = token._listeners.length;
for (i = 0; i < l; i++) {
token._listeners[i](cancel);
}
token._listeners = null;
});
// 给promise添加一个then方法,用于执行用户自定义的函数
this.promise.then = function (onfulfilled) {
var _resolve;
// eslint-disable-next-line func-names
var promise = new Promise(function (resolve) {
token.subscribe(resolve);
_resolve = resolve;
}).then(onfulfilled);
promise.cancel = function reject() {
token.unsubscribe(_resolve);
};
return promise;
};
...
}
// 添加监听器,当已经触发了cancel方法后,不会将监听器加入栈中。
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.prototype.unsubscribe = function unsubscribe(listener) {
if (!this._listeners) {
return;
}
var index = this._listeners.indexOf(listener);
if (index !== -1) {
this._listeners.splice(index, 1);
}
};
httpAdapter
function httpAdapter() {
...
transport = isHttpsProxy ? httpsFollow : httpFollow;
...
var req = transport.request(options, function handleResponse(res) {
if (req.aborted) return;
// 正常这个请求回来的时候就是流式的,IncomingMessage
// 下面的意思是在某几种数据格式情况下会将返回内容解压
var stream = res;
switch (res.headers['content-encoding']) {
/*eslint default-case:0*/
case 'gzip':
case 'compress':
case 'deflate':
stream =
res.statusCode === 204 ? stream : stream.pipe(zlib.createUnzip());
delete res.headers['content-encoding'];
break;
}
var lastRequest = res.req || req;
var response = {
status: res.statusCode,
statusText: res.statusMessage,
headers: res.headers,
config: config,
request: lastRequest,
};
if (config.responseType === 'stream') {
response.data = stream;
settle(resolve, reject, response);
} else {
var responseBuffer = [];
stream.on('data', function handleStreamData(chunk) {
responseBuffer.push(chunk);
if (
config.maxContentLength > -1 &&
Buffer.concat(responseBuffer).length > config.maxContentLength
) {
stream.destroy();
reject(
createError(
'maxContentLength size of ' +
config.maxContentLength +
' exceeded',
config,
null,
lastRequest
)
);
}
});
stream.on('error', function handleStreamError(err) {
if (req.aborted) return;
reject(enhanceError(err, config, null, lastRequest));
});
// 这里面就是关键的如何把流式的数据转化成字符串
stream.on('end', function handleStreamEnd() {
var responseData = Buffer.concat(responseBuffer);
if (config.responseType !== 'arraybuffer') {
responseData = responseData.toString(config.responseEncoding);
}
response.data = responseData;
settle(resolve, reject, response);
});
}
});
}
根据当前协议头对应选用 Follow Redirect
中的 http
或 https
部分,这个模块其实就是原生 http
、https
库上面封装了一层,支持重定向。
至此,axios
的源码解析就结束了。可以发现axios
这个库非常精妙,采用request
方法创建新实例来兼容多种写法;使用promise
链式调用实现拦截器;通过适配浏览器和node
,对外提供了统一的api等。