思维导图
核心代码
request
所有的核心代码就是 request 函数,delete、get、head、options、post、put、patch 都是通过 request 函数来创建的。
- 构建 config
- 构建拦截器
- 执行请求拦截器
- 执行请求
- 执行响应拦截器
构建 config
允许 axios 可以像 axios('url'[, config]) 这样请求,所以第一个参数可能为 string。
如果像这样请求的话,没有给出 config, 那么请求方式默认为 get。
// Allow for axios('example/url'[, config]) a la fetch API
if (typeof config === 'string') {
config = arguments[1] || {};
config.url = arguments[0];
} else {
config = config || {};
}
config = mergeConfig(this.defaults, config);
// Set config.method, default: get
if (config.method) {
config.method = config.method.toLowerCase();
} else if (this.defaults.method) {
config.method = this.defaults.method.toLowerCase();
} else {
config.method = 'get';
}
构建拦截器
在 InterceptorManager.js 这个文件中,提供了拦截器的管理方式。
拦截器的数据结构
interface inteceptor {
fulfilled: function;
rejected: function;
synchronous: boolean;
runWhen // 不知道这个是什么
}
use 方法
我们通过 use 方法来创建一个拦截器。
方法接收三个参数:
fulfilled: 成功的方法rejected: 失败的方法options: 配置参数synchronous: 是否为异步拦截器runWhen: 现在并不知道是啥
通过 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;
};
构建请求拦截器
现在再来看如何构建请求拦截器。
- 判断是否为异步请求拦截器
- 将
fulfilled以及rejected推入到requestInterceptorChain中
以上就完成了对请求拦截器的构建。
// filter out skipped interceptors
// 请新建求拦截器
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 newConfig = config;
// 执行请求拦截器
while (requestInterceptorChain.length) {
var onFulfilled = requestInterceptorChain.shift();
var onRejected = requestInterceptorChain.shift();
try {
newConfig = onFulfilled(newConfig);
} catch (error) {
onRejected(error);
break;
}
}
异步请求拦截器
以下是执行异步请求拦截器的方法
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;
}
执行请求
try {
promise = dispatchRequest(newConfig);
} catch (error) {
return Promise.reject(error);
}
执行响应拦截器
需要注意的是,在 promise 执行完后才能执行响应拦截器,所以应该使用 then 方法。
// 执行响应拦截器
while (responseInterceptorChain.length) {
promise = promise.then(
responseInterceptorChain.shift(),
responseInterceptorChain.shift()
);
}
dispatchRequest
所以可以看出,执行请求的方法就是 dispatchRequest 这个方法
同样此方法也是有顺序的:
- 构建
headers - 转换请求数据
- 发送请求
- 转换响应数据
构建 headers
// Ensure headers exist
config.headers = config.headers || {};
// Flatten headers
config.headers = utils.merge(
config.headers.common || {},
config.headers[config.method] || {},
config.headers
);
转换请求数据
// Transform request data
config.data = transformData.call(
config,
config.data,
config.headers,
config.transformRequest
);
发送请求
需要注意的是此方法内会选择 adapter,默认:浏览器为 XMLHttpRequest,node.js 为 http
return adapter(config).then(function onAdapterResolution(response) {
// ...
return response;
}, function onAdapterRejection(reason) {
// ...
return Promise.reject(reason);
});
转换响应数据
// Transform response data
response.data = transformData.call(
config,
response.data,
response.headers,
config.transformResponse
);
adapter
发送请求的函数在 adapter 中,这里以 xhr 举例。
判断
通过 getDefaultAdapter() 函数来判断什么环境用什么 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;
}
认证
认证比较简单,Basic Authenticatin 认证。将 username 以及 password 两个结合进行 Base64 编码。
// HTTP basic authentication
if (config.auth) {
var username = config.auth.username || '';
var password = config.auth.password
? unescape(encodeURIComponent(config.auth.password))
: '';
requestHeaders.Authorization = 'Basic ' + btoa(username + ':' + password);
}
设置 timeout
表示该请求的最大请求时间,若超出该时间,请求会自动终止。
request.timeout = config.timeout;
新建完成请求时的回调函数
构建 response 的响应数据结构。
function onloadend() {
if (!request) {
return;
}
// Prepare the response
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,
};
settle(resolve, reject, response);
// Clean up request
request = null;
}
设置完成请求时的回调函数
根据浏览器的不同需要进行兼容性处理。
if ('onloadend' in request) {
// Use onloadend if available
request.onloadend = onloadend;
} else {
// Listen for ready state to emulate onloadend
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'
setTimeout(onloadend);
};
}
设置终止回调函数
MDN XMLHttpRequestEventTarget.onabort
request.onabort = function handleAbort() {
if (!request) {
return;
}
reject(createError('Request aborted', config, 'ECONNABORTED', request));
// Clean up request
request = null;
};
设置错误处理回调函数
MDN XMLHttpRequestEventTarget.onerror
// 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(createError('Network Error', config, null, request));
// Clean up request
request = null;
};
设置超时处理回调函数
MDN XMLHttpRequestEventTarget.ontimeout
// Handle timeout
request.ontimeout = function handleTimeout() {
var timeoutErrorMessage = config.timeout
? 'timeout of ' + config.timeout + 'ms exceeded'
: 'timeout exceeded';
if (config.timeoutErrorMessage) {
timeoutErrorMessage = config.timeoutErrorMessage;
}
reject(
createError(
timeoutErrorMessage,
config,
config.transitional && config.transitional.clarifyTimeoutError
? 'ETIMEDOUT'
: 'ECONNABORTED',
request
)
);
// Clean up request
request = null;
};
设置 xsrf
// Add xsrf header
// This is only done if running in a standard browser environment.
// Specifically not if we're in a web worker, or react-native.
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;
}
}
设置请求头
// Add headers to the request
if ('setRequestHeader' in request) {
utils.forEach(requestHeaders, function setRequestHeader(val, key) {
if (
typeof requestData === 'undefined' &&
key.toLowerCase() === 'content-type'
) {
// Remove Content-Type if data is undefined
delete requestHeaders[key];
} else {
// Otherwise add header to the request
request.setRequestHeader(key, val);
}
});
}
设置下载的进度
MDN XMLHttpRequest: progress event
// Handle progress if needed
if (typeof config.onDownloadProgress === 'function') {
request.addEventListener('progress', config.onDownloadProgress);
}
设置上传的进度
MDN XMLHttpRequestEventTarget.upload
// Not all browsers support upload events
if (typeof config.onUploadProgress === 'function' && request.upload) {
request.upload.addEventListener('progress', config.onUploadProgress);
}
取消了请求
直接使用 XMLHttpRequestEventTarget.abort() 方法取消请求。主要为 cancelToken 里面的内容,见下方。
if (config.cancelToken) {
// Handle cancellation
config.cancelToken.promise.then(function onCanceled(cancel) {
if (!request) {
return;
}
request.abort();
reject(cancel);
// Clean up request
request = null;
});
}
发送请求
// Send the request
request.send(requestData);
取消请求
官方的例子是这样使用的:
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 {
// handle error
}
});
axios.post('/user/12345', {
name: 'new name'
}, {
cancelToken: source.token
})
// cancel the request (the message parameter is optional)
source.cancel('Operation canceled by the user.');
首先调用了 source 方法,然后在请求的时候加入了 cancelToken 参数,最后调用 cancel 方法。
source
/**
* Returns an object that contains a new `CancelToken` and a function that, when called,
* cancels the `CancelToken`.
*/
CancelToken.source = function source() {
var cancel;
var token = new CancelToken(function executor(c) {
cancel = c;
});
return {
token: token,
cancel: cancel
};
};
此函数返回了一个对象包含 token 以及 cancel。
token 是一个 CancelToken 实例,参数为一个函数。
CancelToken 构造函数
/**
* A `CancelToken` is an object that can be used to request cancellation of an operation.
*
* @class
* @param {Function} executor The executor function.
*/
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);
});
}
构造函数定义了一个 promise 属性,在 xhr.js 中使用:
if (config.cancelToken) {
// Handle cancellation
config.cancelToken.promise.then(function onCanceled(cancel) {
if (!request) {
return;
}
request.abort();
reject(cancel);
// Clean up request
request = null;
});
}
onCancel 就是取消请求的具体代码。如果没有 request 则直接返回;否则,终止请求后返回。
剩下的内容,之后看心情补充吧。可能会继续添加 xsrf 以及 transformData 的内容