An overview of the
The axios we introduced is actually axios’s prototypical method Request (function object), whose execution context is an instance object of AXIos.
- The instance is extended
Axios
Methods on the constructor prototype, as well as public propertiesdefaults
,interceptors
. - Exposed the
axios.Axios
To allow other classes to inherit. - provides
create
Method is used to create a factory function for a new instance. - provides
Cancel
,CancelToken
、isCancel
Used to interrupt a request. - provides
all
,spread
Method is used to send multiple requests simultaneously.
createInstance()
// lib/axios.js
/** * Create an Axios instance *@param {Object} Default configuration for defaultConfig instance *@return {Axios} Return a new Axios instance */
function createInstance(defaultConfig){
// An axios instance is created primarily as a context for axios execution, and the following extended methods will bind this instance to this
// Why do you do that?
// What's the difference if you just create an instance instead of doing that?
var context = new Axios(defaultConfig)
// Export the request method as an instance to axios.
var instance = bind(Axios.prototype.request, context)
// Extend the methods on the Axios prototype to the instance and bind this of the function to the context instance created above.
Utils.extend(instance, Axios.prototype, context)
// Extend the Axios constructor's public attributes to the instance
Utils.extend(instance, context)
return instance
}
var axios = createInstance(defaults);
module.exports = axios;
// Allow use of default import syntax in TypeScript
module.exports.default = axios;
Copy the code
// lib/helpers/bind.js
function bind(fn, thisArg){
return function warp(){
var args = new Array(arguments.length);
for(var i = 0; i < args.length; i++){
args[i] = arguments[i]
}
return fn.apply(thisArg, args)
}
}
Copy the code
// lib/utils.js
/**
* Extends object a by mutably adding to it the properties of object b.
*
* @param {Object} a The object to be extended
* @param {Object} b The object to copy properties from
* @param {Object} thisArg The object to bind function to
* @return {Object} The resulting value of object a
*/
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
}
})
}
/**
* Iterate over an Array or an Object invoking a function for each item.
*
* If `obj` is an Array callback will be called passing
* the value, index, and complete array for each item.
*
* If 'obj' is an Object callback will be called passing
* the value, key, and complete object for each property.
*
* @param {Object|Array} obj The object to iterate
* @param {Function} fn The callback to invoke for each item
*/
function forEach(obj, fn){
if(obj === null && typeof obj === 'undefined') {return
}
if(typeofobj ! = ='object'){
obj = [obj]
}
if(isArray(obj)){
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)
}
}
}
}
Copy the code
Axios constructor
Public attribute
defaults
Used to save the default configuration items of the Axios libraryinterceptors
containsrequest
和response
Two request interceptors.
Prototype method
request
: The main method used to send a request.getUri
: Get the requestUri
.delete
,get
、head
,options
: alias of the request method, which is actually calledrequest
Methods.post
,put
,patch
: alias of the request method, which is actually calledrequest
Methods.
// lib/core/Axios.js
function Axios(instanceConfig){
this.defaults = instanceConfig;
this.insterceptors = {
requset: new InterceptorManager(),
reqponse: new InterceptorManager()
}
}
Axios.prototype.request = function request(config){
// Either axios(config) or axios(url[, config]) can be called
// Get the requested URL
if(typeof config === 'string'){
config = arguments[1) | | {}; config.url =arguments[0];
}else{
config = config || {}
}
// Merge request configuration information
config = mergeConfig(this.defaults, config)
// Get the request method by default
if(config.method){
config.method = config.method.toLowerCase()
}else if{this.defaults.method}{
config.method = this.defaults.method.toLowerCase()
}else{
config.method = 'get'
}
// The connection interceptor middleware chain is used to register the promise's callback chain
var chain = [dispatchRequest, undefined];
// Generate a successful Promise instance and pass in the request configuration information
var promise = Promise.resolve(config);
// Put the request interceptor at the top of the callback chain
this.interceptors.request.forEach(function unshiftRequestInterceptors(interceptor){
chain.unshift(interceptor.fulfilled, interceptor.rejected)
})
// Put the response interceptor at the end of the callback chain
this.interceptors.response.forEach(function pushRequestInterceptors(interceptors){
chain.push(interceptor.fulfilled, interceptor.rejected)
})
// Bind the callback functions to the Promise callback chain in turn. If an error is reported, the exception will be dropped to the next layer according to the Promise exception pass-through principle
// promise.then(request interceptor).then(send request).then(response interceptor)
while(chain.length){
promise = promise.then(chain.shift(), chain.shift())
}
return promise
}
//
Axios.prototype.getUri = function getUri(config){
config = mergeConfig(this.defaults, config)
return buildURL(congig.url, config.params, config.paramsSerializer).replace(/ ^ \? /.' ')}// Add a request method alias without data to the prototype
utils.forEach(['get'.'head'.'options'.'delete'].function forEachMethodNoData(method){
Axios.prototype[method] = function(url, config){
return this.request(mergeConfig(config || {}, {
method: method,
url: url
}))
}
})
// Add a request method alias containing data to the prototype
utils.forEach(['post'.'put'.'petch'].function forEachMethodWithData(method){
Axios.prototype[method] = function(url, data, config){
return this.request(mergeConfig(config || {}, {
method,
url,
data
}))
}
})
module.export = Axios
Copy the code
The interceptor
Axios provides us with request interceptors and response interceptors. Registered interceptors are stored on the request and Response properties of the public interceptors. Managed by an interceptorManager instance. Let’s look at how the InterceptorManager constructor is implemented.
- By calling the
use
Method stores interceptors on the stack - By calling the
eject
Method to remove interceptors from the stack - By calling the
forEach
Method iterates through interceptors on the stack and registers interceptors.
function InterceptorManager(){
this.handlers = []
}
/** * Add a new interceptor to the stack * Add a new interceptor to the Handlers *@param {Function} fulfilled The function to handle `then` for a `Promise`
* @param {Function} rejected The function to handle `reject` for a `Promise`
*
* @return {Number} An ID used to remove interceptor later * Returns the ID of the interceptor used when removing it
InterceptorManager.prototype.use = function use(fulfilled, rejected){
this.handlers.push({
fulfilled,
rejected
})
return this.handler.length - 1
}
/**
* Remove an interceptor from the stack
*
* @param {Number} id The ID that was returned by `use`
*/
InterceptorManager.prototype.eject = function eject(id){
if(this.handlers[id]){
this.handlers[id] = null}}/** * Iterate over all the registered interceptors * This method is particularly useful for skipping over any * interceptors that may have become 'null' calling `eject`. * *@param {Function} fn The function to call for each interceptor
*/
InterceptorManager.prototype.forEach = function forEach(fn){
utils.forEach(this.handlers, function forEachHandler(h){
if(h ! = =null){
fn(h)
}
})
}
Copy the code
dispatchRequest()
This method is the main method used in Request () to send requests to the server. The return result is also a promise.
function transformData(data, headers, fns){
utils.forEach(fns, function(fn){
data = fn(data, headers)
})
return data
}
function dispatchRequest(config){
// Used to allow the user to manually cancel the request
throwIfCancellationRequested(config)
// Make sure the request header exists
config.headers = config.headers || {};
// Request data conversion
Config. TransformRequest is an array
// This contains the default request data conversion method as well as user-defined additions (if any).
config.data = transformData(
config.data,
config.headers,
config.transformRequest
)
// The request header is flat
confif.headers = utils.merge(
config.headers.common || {},
config.headers[config.method] || {},
config.headers
)
utils.forEach(
['delete'.'get'.'head'.'post'.'put'.'patch'.'common'].function clearHeaderConfig(){
delete config.headers[method]
}
)
var adapter = config.adapter || defaults.adapter;
return adapter(config).then(
function onAdapterResolution(response){
throwIfCancellationRequested(config);
// Convert the response data
response.data = transformData(
response.data,
response.headers,
config.transformResponse
)
return response
},
function onAdapterRejection(reason){
if(! isCancel(reason)){ throwIfCancellationRequestd(config)// Convert the response data
reason.response = transformData(
reason.response.data,
reason.response.headers,
config.transformResponse
)
}
return Promise.reject(reason)
}
)
}
Copy the code
Adapter Request adapter
Axios works with Node.js as well as browsers. So two solutions are also implemented in the method of request.
- Browser usage
XMLHttpRequest
- Node. Js
http
或https
The middleware
function getDefaultAdapter() {
var adapter;
if (typeofXMLHttpRequest ! = ='undefined') {
// For browsers use XHR adapter
adapter = require('./adapters/xhr');
} else if (typeofprocess ! = ='undefined' && Object.prototype.toString.call(process) === '[object process]') {
// For node use HTTP adapter
adapter = require('./adapters/http');
}
return adapter;
}
Copy the code
Let’s take a look at how XMLHttpRequest is handled.
// lib/adapters/xhr
module.exports = function xhrAdapter(config){
return new Promise(function dispatchXhrRequest(resolve, reject){
var requestData = config.data;
var requestHeaders = config.headers;
// FormData
if(utils.isFormData(requestData)){
delete requestHeaders['Content-Type'] // Let the browser set it up
}
if(utils.isBlob(requestData) || utils.isFile(requestData) && requestData.type){
delete requestHeaders['Content-Type'] // Let the browser set it up
}
// Create an asynchronous request
var request = new XMLHttpRequest();
HTTP basic authentication
if(config.auth){
var username = config.auth.username || ' ';
var password = unescape(encodeURIComponent(config.auth.password)) || ' ';
requestHeaders.Authorization = 'Basic' + btoa(username + ':' + password);
}
// Concatenate the complete requested address, if it is an absolute path, directly return.
var fullPath = buildFullPath(config.baseURL, config.url)
request.open(config.method.toUpperCase(),
buildURL(fullPath, config.params, config.paramsSerializer),
true
);
// Set the request timeout in MS
request.timeout = config.timeout;
request.onreadystatechange = function handleLoad(){
// 0 proxy is created, but open() has not yet been called
// 1 open() has been called
// 2 send() has been called, and the header and state are available
// 3 The responseText property in the download already contains some data
// 4 Download complete
// Request status code 4 indicates that the request has completed. Download operation has completed.
if(! request || request.readyState ! = =4) {return;
}
// If the request goes wrong and we don't get a response, it will be handled by onError
With one exception, if the request uses the File: protocol, most browsers will return a status code of 0, even though the request was successful.
if(
request.status === 0 &&
!(request.responseURL && request.responseURL.indexof('file:') = = =0)
){
return;
}
// Prepare response data
// Get the response header
var responseHeaders = 'getAllResponseHeaders' in request ? getAllResponseHeaders() : null
// If the response type is text, take responseText as the result
varresponseData = ! config.responseType || config.reponseType ==='text' ?
request.responseText : request.response;
var response = {
data: responseData,
status: request.status,
statusText: request.statusText,
headers: responseHeaders,
config: config,
request: request
}
// Determine success or failure according to the response code. Users can customize vaildateStatus
settle(resolve, reject, response)
// Clear the request
request = null
}
// Handle browser cancel requests (not manually)
request.onabort = function handleAbort(){
if(! request) {return;
}
reject(createError('Request aborted', config, 'ECONNABORTED', request));
// Clean up request
request = null;
}
// Handle network errors when the network speed is slow
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;
}
// Handle timeout
request.ontimeout = function handleTimeout() {
var timeoutErrorMessage = 'timeout of ' + config.timeout + 'ms exceeded';
if (config.timeoutErrorMessage) {
timeoutErrorMessage = config.timeoutErrorMessage;
}
reject(createError(timeoutErrorMessage, config, 'ECONNABORTED',
request));
// Clean up request
request = null;
};
// 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 requestrequest.setRequestHeader(key, val); }}); }// Add withCredentials to request if needed
if(! utils.isUndefined(config.withCredentials)) { request.withCredentials = !! config.withCredentials; }// Add responseType to request if needed
if (config.responseType) {
try {
request.responseType = config.responseType;
} catch (e) {
// Expected DOMException thrown by browsers not compatible XMLHttpRequest Level 2.
// But, this can be suppressed for 'json' type as it can be parsed by default 'transformResponse' function.
if(config.responseType ! = ='json') {
throwe; }}}// Handle progress if needed
if (typeof config.onDownloadProgress === 'function') {
request.addEventListener('progress', config.onDownloadProgress);
}
// Not all browsers support upload events
if (typeof config.onUploadProgress === 'function' && request.upload) {
request.upload.addEventListener('progress', config.onUploadProgress);
}
if(config.cancelToken){
// Handle cancel events
config.cancelToken.promise.then(function onCanceled(cancel){
if(! request){return
}
request.abort();
reject(cancel);
request = null})}if(! requestData) { requestData =null;
}
// Send the requestrequest.send(requestData); })}Copy the code
Let’s take a look at the success and failure of custom status codes
function settle(resolve, reject, response){
var validateStatus = response.config.validateStatus;
if(! response.status || ! validateStatus || validataStatus(response.status)){ resolve(response) }else{
reject(createError(
'Request failed with status code ' + response.status,
response.config,
null,
response.request,
response
)
)
}
}
// The default validateStatus is
function validateStatus(status){
return status >= 200 && status < 300
}
Copy the code