For the front end, look at the source code and start with Axios, because not only is the logic not too complicated, but the design is clever. After packing, there are only 1,600 lines, picking and choosing, taking a quick look, after removing the notes, there is not much. To make it official: Axios is a Promise-based HTTP library that can be used in browsers and Node.js.
1. How to use axios
1-1. The first type:
To initiate a [delete, get, head, options] request, use:
axios.get('/user',{
headers,
data
}).then(data= >{
//do something
})
Copy the code
To initiate a [POST, PUT, patch] request, use:
axios.post('/user',data,{
headers,
}).then(data= >{
//do something
})
Copy the code
Making the request this way, you can see that Axios is treated as an object with many methods on it, which looks something like this:
let axios = {
delete(){},get(){},head(){},options(){},post(){},put(){},patch(){}}Copy the code
1-2. Second Type:
When sending all types of requests, you can use:
axios('/user', {method:'get',
headers,
}).then(data= >{
//do something
})
Copy the code
or
axios({
url:'/user'.method:'get',
headers,
}).then(data= >{
//do something
})
Copy the code
Of course, a GET request can be simplified like this:
axios('/user').then(data= >{
//do something
})
Copy the code
Making the request this way, you can see that Axios is treated as a function and looks something like this:
function axios(url,config) {
//do something
}
Copy the code
2. What is Axios
2-1 axios is both an object and a method
Axios can be either an object or a function. When used as an object, it has many request methods attached to it, such as get, POST, head, and so on. When used as a function, it can be called directly, passing configuration parameters in two forms, namely axios(URL [,config]) and axios(config). To do that, let’s look at the function.
2-2. Three roles of functions:
Functions are interesting things to think about in Javascript, for example, they have three roles:
Type 1: ordinary functions
A function is a normal function, and this role is common enough that we use it a lot. For example, define a function foo and call it like this:
/ / define
function foo(){
//do something
}
/ / call
foo()
Copy the code
2-2-2. Second: constructor
A function can also be used as a constructor, such as defining a constructor Foo and then getting an instance of it, as follows:
/ / define
function Foo(){
//do something
}
// Get an instance
let instance=new Foo()
Copy the code
Capitalizing a function is purely for the sake of conforming to a language, a standard, a convention, and nothing else.
2-2-3. The third type: objects
You can use a function as an object, you can give it attributes, you can get its attribute values, and it has nothing to do with its other identity. So if I have a function foo, I’m not going to treat you like a function, I’m going to treat you like an object, I’m going to define properties for it, I’m going to get its property values, ok, nothing wrong.
function foo(){
//do something
}
// Give foo an attribute of a number
foo.a=1;
// Define an attribute for foo with a value of function
foo.b=function(){};
// Get the value of foo's property A
console.log(foo.a)
Copy the code
Now, I want to treat foo like a function again, okay, you’re the director, you’re the boss.
// Now call foo as a function
foo()
Copy the code
With that in mind, let’s take a look at how Axios can be used in multiple ways.
2-3. How to use axios source code in multiple ways
The source code is as follows:
function createInstance(defaultConfig) {
// Get an instance of Axios
var context = new Axios(defaultConfig);
// Bind the context of the Request method on the Axios prototype (that is, this) to the newly created instance
var instance = bind(Axios.prototype.request, context);
// Extend properties and methods from the Axios prototype to instance
utils.extend(instance, Axios.prototype, context);
// Extend properties and methods on the created instance to instance
utils.extend(instance, context);
/ / returns the instance
return instance;
}
//axios is the instance above
var axios = createInstance(defaults);
/ /...
module.exports = axios;
Copy the code
In the above code, axios.prototype. request is a method that binds its context (this) to an instance of Axios using the utility function bind. So instance is a request method bound to this, and now it’s time to do things around instance. Instance itself is a function, but here it is treated as an object, with properties and methods defined on it.
Axios. Prototype extends properties and methods to instance.
// Define the request method, which is the focus of all other calls
Axios.prototype.request = function request(config){/ *... * /}
// Batch define methods on prototypes that do not receive request body data, such as GET requests
utils.forEach(['delete'.'get'.'head'.'options'].function forEachMethodNoData(method) {
Axios.prototype[method] = function(url, config) {/ *... * /};
});
// Batch define methods on prototypes that receive request body data, such as POST requests
utils.forEach(['post'.'put'.'patch'].function forEachMethodWithData(method) {
Axios.prototype[method] = function(url, data, config) {/ *... * /};
});
Copy the code
The above code defines the methods on the prototype of Axios. First define the request method, then batch define the [delete, GET, head, options] methods, and finally batch define the [POST, PUT, patch] methods. This means that the methods defined on the prototype will eventually be extended to instance, where we can use axios.get(), axios.post(), and so on. In fact, whether we call AXIos as an object or axios as a function, we end up calling the prototypical Request method.
3. Merge sequence of Config
Now that you know how AXIos can be used in multiple ways, let’s take a look at configuration in AXIos, where the configuration items passed in by the user go through the entire process.
3-1, passaxios.get()
And so no request body method passed in the configuration
When we call axios like this:
axios.get('/user',{headers,timeout,... })Copy the code
The axios source code does a layer of processing for the configuration we pass in. This layer of processing is very simple: first determine whether to pass config, then merge method and URL into config, and finally call request and pass the processed configuration to Request. The source code is as follows:
utils.forEach(['delete'.'get'.'head'.'options'].function forEachMethodNoData(method) {
Axios.prototype[method] = function(url, config) {
return this.request(utils.merge(config || {}, {
method: method,
url: url
}));
};
});
Copy the code
3-2, throughaxios.post()
Wait for the configuration passed in by the request body method
When we call axios like this:
axios.post('/user', {name:'wz'},{headers,timeout,... })Copy the code
The internal processing of AXIos is almost identical to the above call to GET, except that the request body data is processed a little bit more. The source code is as follows:
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 // Process the request body parameters
}));
};
});
Copy the code
3-3,Axios.prototype.request
What is done to the configuration in the method
Axios.get (), axios.post, and axios(), either of which will eventually call the request method.
Axios.prototype.request = function request(config) {
if (typeof config === 'string') {
config = utils.merge({
url: arguments[0]},arguments[1]);
}
/ /...
// Here you can see the configuration priority order, from left to right
config = utils.merge(defaults, {method: 'get'}, this.defaults, config);
// Change the request method to lowercase
config.method = config.method.toLowerCase();
/ /...
};
Copy the code
If the first argument is a string, which is what we use, axios(‘/user’,{… }), puts the string on the URL property of a new object, and then merges it with the second parameter. The next step is to merge multiple configurations in order of weight from smallest to largest.
defaults
isaxios
Is the default configuration with the lowest weight.{method:'get'}
Is to set the default request method.this.defaults
Is newly createdaxios
Instance of the configuration object passed in.axios.create({... })
config
, the callaxios
Is the parameter passed with the highest weight.
At this point, the processing of config inside AXIos is complete. The config is then handed over to the request interceptor in turn. Let’s take a look at the interceptor in AXIos.
4. Organize interceptors, converters, adapters
Take a look at the relationship between these three things, as shown below:
There can be multiple interceptors and converters with different responsibilities
The request interceptor’s job is to handle request configuration
The converter processes the request body data
The purpose of the adapter is to determine which request method to use depending on the environment that the browser environment usesXMLHttpRequest
.node
Environment usehttp(s).request()
Methods; The response interceptor processes the response data.
4-1. Interceptor
An interceptor is a function that comes in two types: Request interceptor and the interceptor, the request is the main purpose of interceptor, provides a mechanism that allows us to focus on a variety of request parameters passed, people like to take the subway, no matter where you come from, what things in hand, take the subway will be through the security check, as long as the interceptor is just like this security check, Each security check has its own security check task. After the first security check has passed, it will be transferred to the second security check, and the third security check until all the security check has passed, can not arrive at the waiting area.
Source code for interceptors in AXIos:
function InterceptorManager() {
// A container for interceptors
this.handlers = [];
}
InterceptorManager.prototype.use = function use(fulfilled, rejected) {
// Put the interceptor passed in by the user into the container,
this.handlers.push({
fulfilled: fulfilled,
rejected: rejected
});
// Returns the position of the interceptor in the container, which can be used to cancel the interceptor
return this.handlers.length - 1;
};
InterceptorManager.prototype.eject = function eject(id) {
if (this.handlers[id]) {
// Remove the specified interceptor by its position in the container
this.handlers[id] = null; }}; InterceptorManager.prototype.forEach =function forEach(fn) {
utils.forEach(this.handlers, function forEachHandler(h) {
if(h ! = =null) {
fn(h);// Pass each interceptor traversed back to the outside}}); };Copy the code
The interceptor source code is easy to understand. The InterceptorManager constructor prototype defines three methods for adding, removing, and traversing interceptors. Note that each interceptor is divided into two methods, success and failure. This is because AXIos implements chained calls based on promises, so each interceptor’s success and failure methods are passed separately into the Promise’s then method. If you’re not familiar with Promise, check it out here
The responder interceptor works the same way as the request interceptor, except that the responder interceptor intercepts the returned data, while the request interceptor intercepts the configuration of the request. Next comes the converter.
4-2 converter
A converter is a function that comes in two types: request data converter and response data converter. The request converter function takes two parameters, data and headers, to process the request body and headers. Response data converters work primarily with response data, such as converting JSON data to normal data. The default converter in the source code:
let defaults={
// Request a data converter
transformRequest: [function transformRequest(data, headers) {
normalizeHeaderName(headers, 'Content-Type');
// If the request body data type is one of the following, return it directly
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);
}
returndata; }].// Response data converter
transformResponse: [function transformResponse(data) {
if (typeof data === 'string') {
try {
data = JSON.parse(data);
} catch (e) { /* Ignore */}}returndata; }}],Copy the code
In fact, if you think about it carefully, you can find that the converter can do the same thing in the interceptor, but for the sake of a single responsibility, a clear division of labor, the data processing part of the work in the converter to do. The next step is to send the request, which is done by the adapter.
4-3. Adapters
The adapter decides which tool to use to send the request based on the current environment in which axios is used, XMLHttpRequest in a browser or HTTP (s).request() in node. The source code is as follows:
function getDefaultAdapter() {
var adapter;
if (typeofXMLHttpRequest ! = ='undefined') {
// The browser environment, using the XHR adapter
adapter = require('./adapters/xhr');
} else if (typeofprocess ! = ='undefined') {
// Node environment, using HTTP adapter
adapter = require('./adapters/http');
}
return adapter;
}
Copy the code
In the./ Adapters/XHR file, in addition to checking IE, you can basically send AJAX steps as follows:
// Get the XMLHttpRequest instance
let xhr = new new XMLHttpRequest();
// Determine parameters such as request methodxhr.open(method,url,...) ;// Listen for request status
xhr.onreadystatechange = function () {
/ /...
}
// Send the request
xhr.send();
Copy the code
In the./adapters/ HTTP file, use the Node package HTTP and HTTPS to complete the request.
4-4. How to chain invoke interceptor/converter/adapter
To see how axios organizes interceptors, converters, and adapters, see the source code:
Axios.prototype.request = function request(config) {
// Chain stores all interceptors, converters, and adapters
var chain = [dispatchRequest, undefined];
// Generate a successful promise with a sequence of configurations
var promise = Promise.resolve(config);
/ / this. Interceptors. Request to deposit all the request of the interceptor
this.interceptors.request.forEach(function unshiftRequestInterceptors(interceptor) {
// Put all request interceptors into the chain container, using unshift,
That is, request interceptors are placed from the head of the chain container.
chain.unshift(interceptor.fulfilled, interceptor.rejected);
});
/ / this. Interceptors. The response to deposit all the response of the interceptor
this.interceptors.response.forEach(function pushResponseInterceptors(interceptor) {
// Place all response interceptors in chain, using push,
That is, responder interceptors are placed from the end of the chain container.
chain.push(interceptor.fulfilled, interceptor.rejected);
});
while (chain.length) {
// Call the interceptor, converter, adapter in the chain container
//chain.shift(); //chain.shift();
promise = promise.then(chain.shift(), chain.shift());
}
return promise;
};
Copy the code
DispatchRequest is initialized with undefined and dispatchRequest is initialized with dispatchRequest
5,dispatchRequest
What did you do
There are only a few things left from sending a request to getting here,
- Request data converter
- Request adapter
- Response data converter
DispatchRequest basically does these three things. The simplified source code is as follows:
module.exports = function dispatchRequest(config) {
// Convert request data
config.data = transformData(
config.data,
config.headers,
config.transformRequest
);
var adapter = config.adapter || defaults.adapter;
The Adapter method selects the specific request method based on the environment in which the request method is invoked.
return adapter(config).then(function onAdapterResolution(response) {
// Convert the response data
response.data = transformData(
response.data,
response.headers,
config.transformResponse
);
return response;
}, function onAdapterRejection(reason) {/ *... * /});
};
Copy the code
6. Overall process
As a reminder, the journey from sending a request to successfully receiving a response is all in the picture.