Axios, needless to say, is a common library for sending HTTP requests in front-end development, as anyone who has used it knows. This article is used to sort out the encapsulation use of Axios commonly used in projects. Meanwhile, I learned source code and realized Axios core code by hand. If there is any mistake or not precise place, please give correction, thank you very much. If you like or inspired, welcome to like, to the author is also a kind of encouragement ❤️
Axios uses encapsulation
What is the
Axios is a Promise-based HTTP library that can be used in browsers and Node.js. Its features:
- Created from the browser
XMLHttpRequests
- From the node. Js to create
http
request - support
Promise
API - Intercept requests and responses
- Transform request data and response data
- Cancel the request
- Automatically convert JSON data
- The client supports XSRF defense
Official website: www.axios-js.com/zh-cn/docs/…
There are two ways to use Axios: one is to use the global Axios object directly; The other is to use the axios.create(config) method to create an instance object. The difference between the two approaches is that instance objects created in the second approach are cleaner; The global Axios object is actually exported from the created instance object, which itself loads many default properties. Later source code learning will be detailed.
request
Axios, the HTTP library, is flexible and gives users multiple ways to send requests, which can be confusing. Careful sorting reveals that the global Axios (or the object created by axios.create(config)) can be used as either an object or a function:
// axios is used as an object
axios.request(config)
axios.get(url[, config])
axios.post(url[, data[, config]])
Copy the code
// axios() is used as a function. Send a POST request
axios({
method: 'post'.url: '/user/12345'.data: {
firstName: 'Fred'.lastName: 'Flintstone'}});Copy the code
I’ll explain why Axios can be used both ways later in the source code.
Cancel the request
Cancel token can be created using the canceltoken. source factory method:
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 {
// Processing error}});// Cancel the request (the message argument is optional)
source.cancel('Operation canceled by the user.');
Copy the code
Source has two attributes: source.token identifies the request; The other is the source.cancel() method, which, when called, changes the Promise state of the CancelToken instance to resolved, triggering the XHR object’s Abort () method to cancel the request.
intercept
Axios also has a fantastic feature point that intercepts the request before it is sent, and intercepts the result. In a service scenario, after you log in to the midstage system and obtain the token returned by the backend, you can add the token to the header. The customized header is added to all subsequent requests.
// Interception 1 requests interception
instance.interceptors.request.use(function(config){
// What to do before sending the request
const token = sessionStorage.getItem('token');
if(token){
constnewConfig = { ... config,headers: {
token: token
}
}
return newConfig;
}else{
returnconfig; }},function(error){
// What to do about the request error
return Promise.reject(error);
});
Copy the code
We can also use the request interception function to cancel repeated requests, that is, before the previous request has not been returned, the user needs to cancel the previous request first, and then send a new request. For example, the search box is automatically queried. When a user changes the content and sends a request again, the user needs to cancel the previous request to avoid request and response confusion. Another example is the form submit button. If the user clicks the submit button several times, we need to cancel the previous request to ensure that only one request is sent and responded to.
The implementation principle is to use an object record has been sent to the request, in the request interception function to judge whether this object record the request information, if there is already, cancel the previous request, the request will be added to the object; If the request has not been recorded, the request information is added to the object. When the final request is complete, the logic to delete the request information is performed in the response interception function.
// Intercept 2 duplicate requests and cancel the previous request
const promiseArr = {};
instance.interceptors.request.use(function(config){
console.log(Object.keys(promiseArr).length)
// What to do before sending the request
let source=null;
if(config.cancelToken){
// Config contains the source information
source = config.source;
}else{
const CancelToken = axios.CancelToken;
source = CancelToken.source();
config.cancelToken = source.token;
}
const currentKey = getRequestSymbol(config);
if(promiseArr[currentKey]){
const tmp = promiseArr[currentKey];
tmp.cancel("Cancel previous request");
delete promiseArr[currentKey];
promiseArr[currentKey] = source;
}else{
promiseArr[currentKey] = source;
}
return config;
}, function(error){
// What to do about the request error
return Promise.reject(error);
});
// Create a unique identifier based on the url, method, and params
function getRequestSymbol(config){
const arr = [];
if(config.params){
const data = config.params;
for(let key of Object.keys(data)){
arr.push(key+"&"+data[key]);
}
arr.sort();
}
return config.url+config.method+arr.join("");
}
instance.interceptors.response.use(function(response){
const currentKey = getRequestSymbol(response.config);
delete promiseArr[currentKey];
return response;
}, function(error){
// What to do about the request error
return Promise.reject(error);
});
Copy the code
Finally, we can unify the logic of the return code in response interceptor functions:
// Response interception
instance.interceptors.response.use(function(response){
// 401 No login to the login page
if(response.data.code===401) {window.location.href = "http://127.0.0.1:8080/#/login";
}else if(response.data.code===403) {// 403 No Permission The no permission page is displayed
window.location.href = "http://127.0.0.1:8080/#/noAuth";
}
return response;
}, function(error){
// What to do about the request error
return Promise.reject(error);
})
Copy the code
File download
There are two ways to download a file. One is to download the file directly from the external address on the server. Another option is to download files as binary streams through an interface.
The first type: under the same domain name using a tag download:
// httpServer.js
const express = require("express");
const path = require('path');
const app = express();
// Static file address
app.use(express.static(path.join(__dirname, 'public')))
app.use(express.static(path.join(__dirname, '.. / ')));
app.listen(8081.() = > {
console.log("Server started successfully!")});Copy the code
// index.html
<a href="test.txt" download="test.txt"> download < / a >Copy the code
Second: binary file stream transmission, we directly access the interface and can not download files, to a certain extent to ensure the security of data. Most scenarios are: the back end receives query parameters, queries the database and dynamically generates Excel files through plug-ins, which are downloaded by the front end in the form of file streams.
At this point, we can encapsulate the logic that requested the file download. Store the binary stream in a Blob object, turn it into a URL object, and download it with the A tag.
// Encapsulate downloads
export function downLoadFetch(url, params = {}, config={}) {
/ / cancel
const downSource = axios.CancelToken.source();
document.getElementById('downAnimate').style.display = 'block';
document.getElementById('cancelBtn').addEventListener('click'.function(){
downSource.cancel("User cancels download");
document.getElementById('downAnimate').style.display = 'none';
}, false);
/ / parameters
config.params = params;
// The timeout period
config.timeout = config.timeout ? config.timeout : defaultDownConfig.timeout;
/ / type
config.responseType = defaultDownConfig.responseType;
// Cancel the download
config.cancelToken = downSource.token;
return instance.get(url, config).then(response= >{
const content = response.data;
const url = window.URL.createObjectURL(new Blob([content]));
// Create a tag
const link = document.createElement('a');
link.style.display = 'none';
link.href = url;
Content-disposition: attachment; filename=download.txt
const filename = response.headers['content-disposition'].split(";") [1].split("=") [1];
link.download = filename;
document.body.appendChild(link);
link.click();
document.body.removeChild(link);
return {
status: 200.success: true}})}Copy the code
Juejin. Cn/post / 687891…
Write Axios core code by hand
After all this usage writing, I finally got down to business, writing Axios core code by hand. The Axios library source code is not difficult to read, not particularly complex logic, you can rest assured to read 😂.
Json file of axios module, where “main”: “index.js” is the file entry. Step by step we can see how the source code is strung together.
Imitating the directory structure above, we create our own directory structure:
├ ─ garbage, ├ ─ 2nd class, ├ ─ 2nd class, 2nd class, 2nd class, 2nd class, 2nd class, 2nd class, 2nd class, 2nd class, 2nd class, 2nd class, 2nd class, 2nd class, 2nd class, 2nd classCopy the code
What is Axios
As mentioned above, the global Axios object we use is actually a constructed Axios, which can be used as an object calling get, POST, etc., or directly as a function. This is because the global Axios is actually the function object instance. The source code is located in axios/lib/axios.js. The specific code is as follows:
// axios/lib/axios.js
// Create an axios instance
function createInstance(defaultConfig) {
var context = new Axios(defaultConfig);
// Instance object is a function returned by bind
var instance = bind(Axios.prototype.request, context);
// Copy axios.prototype to instance
utils.extend(instance, Axios.prototype, context);
// Copy context to instance
utils.extend(instance, context);
return instance;
}
// Instantiate an axios
var axios = createInstance(defaults);
// Add Axios attributes to this instance
axios.Axios = Axios;
// Add the create method to this instance
axios.create = function create(instanceConfig) {
return createInstance(mergeConfig(axios.defaults, instanceConfig));
};
// Add the CancelToken method to this instance
axios.CancelToken = require('./cancel/CancelToken');
// Export the instance axios
module.exports.default = axios;
Copy the code
We can use axios.js and axiosinstance.js as shorthand:
// Axios.js
/ / Axios subject
function Axios(config){}// The core method sends the request
Axios.prototype.request = function(config){
}
Axios.prototype.get = function(url, config={}){
return this.request({url: url, method: 'GET'. config}); } Axios.prototype.post =function(url, data, config={}){
return this.request({url: url, method: 'POST'.data: data, ... config}) }export default Axios;
Copy the code
In the axiosinstance. js file, instantiate an Axios to get the context, bind the methods on the prototype object to the instance object, and add the context properties to the instance object. Thus instance becomes a function object. It can be used as either an object or a function.
// axiosInstance.js
// Create an instance
function createInstance(config){
const context = new Axios(config);
var instance = Axios.prototype.request.bind(context);
// Extend the axios.prototype property to instance
for(let k of Object.keys(Axios.prototype)){
instance[k] = Axios.prototype[k].bind(context);
}
// Extend the context property to instance
for(let k of Object.keys(context)){
instance[k] = context[k]
}
return instance;
}
const axios = createInstance({});
axios.create = function(config){
return createInstance(config);
}
export default axios;
Copy the code
Namely axios. Js export axios object is not a new axios () method returns the object context, but axios. Prototype. Request. Bind (context) execution returns the instance, Iterate through axios. prototype and change its this to point to context; Iterating over the context gives instance all the properties of the context. This way the instance object is invincible: 😎 has all the methods on Axios.prototype and all the properties of the context.
Request to implement
We know that Axios creates an XMLHttpRequest object in the browser and an HTTP send request in the Node.js environment. Axios.prototype.request() is the core method for sending requests. This method actually calls dispatchRequest, And dispatchRequest method invocation is config. The adapter | | defaults. The adapter is custom adapter or the default defaults. Adapter, By default, defaults.adapter calls the getDefaultAdapter method.
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
The getDefaultAdapter method ends up returning different implementations depending on the current environment, using the adapter pattern. We simply implement XHR to send the request:
// Adapter.js
function getDefaultAdapter(){
var adapter;
if(typeofXMLHttpRequest ! = ='undefined') {// Import the XHR object request
adapter = (config) = >{
returnxhrAdapter(config); }}return adapter;
}
function xhrAdapter(config){
return new Promise((resolve, reject) = >{
var xhr = new XMLHttpRequest();
xhr.open(config.method, config.url, true);
xhr.send();
xhr.onreadystatechange = () = >{
if(xhr.readyState===4) {if(xhr.status>=200&&xhr.status<300){
resolve({
data: {},
status: xhr.status,
statusText: xhr.statusText,
xhr: xhr
})
}else{
reject({
status: xhr.status }) } } }; })}export default getDefaultAdapter;
Copy the code
This makes sense: the getDefaultAdapter method returns a Promise object each time it executes, so the axios.prototype. request method gets a Promise object that executes the XHR sent request.
Add the method to send the request to our axios.js:
//Axios.js
import getDefaultAdapter from './adapter.js';
Axios.prototype.request = function(config){
const adapter = getDefaultAdapter(config);
var promise = Promise.resolve(config);
var chain = [adapter, undefined];
while(chain.length){
promise = promise.then(chain.shift(), chain.shift());
}
return promise;
}
Copy the code
Interceptor implementation
Here’s how interceptors workAxios.prototype.request
In the methodchain
Array to add request interceptor functions tochain
In front of the array, add the response interceptor function to the end of the array. In this way, the effect of pre-send interception and post-response interception can be achieved.Create InterceptorManager. Js
//InterceptorManager.js
/ / the interceptor
function InterceptorManager(){
this.handlers = [];
}
InterceptorManager.prototype.use = function(fulfilled, rejected){
this.handlers.push({
fulfilled: fulfilled,
rejected: rejected
});
return this.handlers.length -1;
}
export default InterceptorManager;
Copy the code
In the axios.js file, the constructor has the interceptors attribute:
//Axios.js
function Axios(config){
this.interceptors = {
request: new InterceptorManager(),
response: new InterceptorManager()
}
}
Copy the code
So we add a handle to the interceptor in the axios.prototype. request method:
//Axios.js
Axios.prototype.request = function(config){
const adapter = getDefaultAdapter(config);
var promise = Promise.resolve(config);
var chain = [adapter, undefined];
// Request interception
this.interceptors.request.handlers.forEach(item= >{
chain.unshift(item.rejected);
chain.unshift(item.fulfilled);
});
// Response interception
this.interceptors.response.handlers.forEach(item= >{
chain.push(item.fulfilled);
chain.push(item.rejected)
});
console.dir(chain);
while(chain.length){
promise = promise.then(chain.shift(), chain.shift());
}
return promise;
}
Copy the code
So interceptors are executed in order: Request interception 2 -> Request interception 1 -> Send request -> Response interception 1 -> Response interception 2
Cancel the request
Here comes the best part of Axios: Cancel the request. We know XHR’s xhr.abort(); The function cancels the request. So when does this cancel request go ahead? There must be a signal telling the XHR object when to cancel. Cancellation requests are something to do at some point in the future, what can you think of? Yeah, Promise. The Promise’s then method will only execute if the Promise object’s state becomes Resolved. We can use this feature to cancel the request in the then method of the Promise object. Look at the code:
//CancelToken.js
// Cancel the request
function CancelToken(executor){
if(typeofexecutor ! = ='function') {throw new TypeError('executor must be a function.')}var resolvePromise;
this.promise = new Promise((resolve) = >{
resolvePromise = resolve;
});
executor(resolvePromise)
}
CancelToken.source = function(){
var cancel;
var token = new CancelToken((c) = >{
cancel = c;
})
return {
token,
cancel
};
}
export default CancelToken;
Copy the code
When we call const source = canceltoken.source (), the source object has two fields, one is the token object and the other is the cancel function. In the XHR request:
/ / adapter
// adapter.js
function xhrAdapter(config){
return new Promise((resolve, reject) = >{...// Cancel the request
if(config.cancelToken){
// This will be done when resolved
config.cancelToken.promise.then(function onCanceled(cancel){
if(! xhr){return;
}
xhr.abort();
reject("Request has been canceled.");
// clean up xhr
xhr = null; })}})}Copy the code
The CancelToken constructor needs a function passed in to expose the resolve function, which controls the internal Promise, to the Source cancel function. This allows the internal Promise state to be controlled by the source.cancel() method, seconds ~ 👍
Node back-end port
Node backend simple interface code:
const express = require("express");
const bodyParser = require('body-parser');
const app = express();
const router = express.Router();
// File download
const fs = require("fs");
/ / get request
router.get("/getCount".(req, res) = >{
setTimeout(() = >{
res.json({
success: true.code: 200.data: 100})},1000)})// Binary file stream
router.get('/downFile'.(req, res, next) = > {
var name = 'download.txt';
var path = '/' + name;
var size = fs.statSync(path).size;
var f = fs.createReadStream(path);
res.writeHead(200, {
'Content-Type': 'application/force-download'.'Content-Disposition': 'attachment; filename=' + name,
'Content-Length': size
});
f.pipe(res);
})
// Set cross-domain access
app.all("*".function (request, response, next) {
* indicates that any domain name is allowed to cross domains. http://localhost:8080 indicates the Origin address of the front-end request
response.header("Access-Control-Allow-Origin"."http://127.0.0.1:5500");
// Set what attributes can be added to the request header
response.header('Access-Control-Allow-Headers'.'Content-Type, Content-Length, Authorization, Accept, X-Requested-With');
/ / exposed to axios https://blog.csdn.net/w345731923/article/details/114067074
response.header("Access-Control-Expose-Headers"."Content-Disposition");
// Set Cookie information to be carried across domains
response.header('Access-Control-Allow-Credentials'."true");
// Sets which methods in the request header are valid
response.header(
"Access-Control-Allow-Methods"."PUT,POST,GET,DELETE,OPTIONS"
);
response.header("Content-Type"."application/json; charset=utf-8");
next();
});
// Interface data parsing
app.use(bodyParser.json())
app.use(bodyParser.urlencoded({
extended: false
}))
app.use('/api', router) // Route registration
app.listen(8081.() = > {
console.log("Server started successfully!")});Copy the code
Git address
If we can follow the source code to knock again, I believe there will be a lot of harvest.
Handwritten Axios core code Github address: github.com/YY88Xu/axio… Axios Package: github.com/YY88Xu/vue2…