“This is the first day of my participation in the Gwen Challenge in November. See details of the event: The last Gwen Challenge in 2021”.

Learn why

Axios is an important ajax request library in the front-end domain. Whether it is Vue, React or Node, axios can be used to make Ajax requests. It can be said that AXIos is a very important part of the front-end. Mastering axios is of great significance to our daily development and technical learning.

Axios Chinese document

Learning content

  • Learn how AXIos sends requests and implements them
  • Learn how the Axios interceptor works
  • Learn about axios cancel requests
  • Simulate the last three functions

Learn to prepare

The following preparations are to prepare for the implementation of the simulation function.

  1. Use the JSON-server package in NPM to create a static server that provides the interface we need for development.
  2. You can select global installationnpm install -g json-server
  3. Before creating adb.jsonfile
  4. Add the following to the pathdb.jsonIn the
    "posts": [
        {
            "id": 1,
            "title": "json-server",
            "author": "typicode"
        },
        {
            "id": 2,
            "title": "json-server2",
            "author": "typicode2"
        }
    ],
    "comments": [
        {
            "id": 1,
            "body": "some comment",
            "postId": 1
        }
    ],
    "profile": {
        "name": "typicode"
    }
}
Copy the code
  1. usejson-server --watch db.jsonStart the service and you can access the response resources on port 3000http://localhost:3000/posts

start

  • Open the vscode terminal to download the axios packagenpm i axios, the resulting axios main directory is:
├ ─ ─ / dist / # project output directory ├ ─ ─ / lib / # project source directory │ ├ ─ ─ / cancel / # define cancel function │ ├ ─ ─ / core / # some core functions │ │ ├ ─ ─ Axios. The core of js # Axios main class │ │ ├─ ├─ interceptorMan.js # ├─ ├─ sole.js # ├─ ├─ sole.js # Change the status of Promise │ ├ ─ ─ some auxiliary methods/helpers / # │ ├ ─ ─ / adapters / # define request adapter XHR, HTTP │ │ ├ ─ ─ HTTP. Js # realize the HTTP adapter │ │ └ ─ ─ XHR. Js # ├─ ├── ├.js # ├── ├.js # ├── download. json # ├─ ├.ts # ├ ─ index.js # import file to configure TypeScript declarationsCopy the code

Basic use of Axios

  • In the Axios Chinese documentation we can see that Axios has a lot of apis,

Axios (config), axios.get(config), axios.post(config), axios.creat(config)…

The running process of Axios

The creation process of Axios

  • Why can AXIos be used as a function and also as an object?

  • The main core is /lib/axios, which is where we can find the answer

// axios.js ... Function createInstance(defaultConfig) {/* Create an instance of Axios. Get ()/post()/put()/delete()/request() has two important attributes: defaults/interceptors */ var context = new Axios(defaultConfig); / / axios and axios. The create () is the corresponding function request, / / axios. Prototype. Request. Bind var instance = (context) bind(Axios.prototype.request, context); // copy the method on the axios prototype object to instance: request()/get()/post()/put()/delete() utils.extend(instance, Axios.prototype, context); // Copy attributes from the Axios instance object to instance: the defaults and interceptors attributes utils.extend(instance, context); // Factory for creating new instances instance.create = function create(instanceConfig) { return createInstance(mergeConfig(defaultConfig, instanceConfig)); }; return instance; } // Create the default instance to be exported var axios = createInstance(defaults); .Copy the code
  • Axios.js
Function Axios(instanceConfig) {// Save the specified config as the defaults property this.defaults = instanceConfig; // save the object containing the request/response InterceptorManager as the interceptors attribute this.interceptors = {request: new InterceptorManager(), response: new InterceptorManager() }; } Axios.prototype.request = function request(config) { ... }; Axios.prototype.getUri = function getUri(config) {... }; ForEach (['delete', 'get', 'head', 'options'], function forEachMethodNoData(method) {... });Copy the code
  1. Axios is the object returned by the createInstance function
  2. In createInstan, define an Axios instance context, bind this from the Request method on the Axios prototype to the context and assign it to instance using bind. Make axios usable as a function (axios()…)
  3. Copy the methods on the Axios prototype object to instance so that Axios can be used as an object (axios.get()…)
  4. Copy attributes from Axios instance to instance. Axios can use interceptor (axios. Interceptors. Request. Use ()…).
  5. Finally, hand the create method to Instance so that it can be used recursively to extend the client. In cases where you are connected to multiple remote devices that have some common ground, but not all of them, you can repackage the constructs against already constructed instances, providing deep construction controller capability

Simulate the creation of Axios

Function Axios(config) {// Initialize this.defaults = config // In order to create the default attribute this.intercepters = {request: {}, Response: Request = function (config) {console.log(" send ajax request "+ config.method); } Axios.prototype.get = function (config) { return Axios.prototype.request({method:"GET"}) } Axios.prototype.post = function (config) { return Axios.prototype.request({ method: Function createInstance(config){let context = new Axios(config) // context You can use the method on the Axios prototype, Such as the context. The get () constext. Post, but cannot be used as a function of the context () / / create request function let the instance = Axios. Prototype. Request. Bind (context) // Bind creates a new function. Instance is already a function, and you can use instance to send a request, but you can only use the request function, and it cannot be used as an object. It doesn't have the get, POST, put, and defaults // intercepters attributes to add methods from the axios. prototype object to the instance object Object.keys(Axios.prototype).forEach(key =>{ instance[key] = Axios.prototype[key].bind(context) // // add attributes default, this.intercepters}); // add attributes default, this.intercepters}); Intercepters object.keys (context).foreach (key => {instance[key] = context [key]}) // Test let axios = createInstance() // Send request axios({method:"GEt"}) axios({method:"POST"}) axios.get({}) axios.post({})Copy the code
  • Neither Request nor XHR requests are implemented here, as simulated below

Axios request process

  • Axios () is the axios.prototype. request method.

  • In axios.js, the current axios.prototype. request has been modified. The author reconstructs the previous code to avoid interceptor asynchronism or long macro task execution because the request is executed in the microtask, which is created before the promise chain is built. If the macro task takes a long time before the request is executed, or if a request interceptor is asynchronous, the actual Ajax request will be sent with some delay, so it is necessary to resolve this issue.

  • In order to better understand the changes after PR, and the reasons for the changes, let’s first look at the changes before the revision.

  • Previous Axios. Prototype. The request

Axios.prototype.request = function request(config) { /*eslint no-param-reassign:0*/ // Allow for axios('example/url'[, config]) a la fetch API if (typeof config === 'string') { config = arguments[1] || {}; config.url = arguments[0]; } else { config = config || {}; } // mergeconfig. js config = mergeConfig(this.defaults, config); Method = config.method? config.method.toLowerCase() : 'get'; Var chain = [dispatchRequest, undefined]; var promise = Promise.resolve(config); / / after add request interceptor save in front of the array. This interceptors. Request. ForEach (function unshiftRequestInterceptors (interceptor) { chain.unshift(interceptor.fulfilled, interceptor.rejected); }); / / add the response after the back of the interceptor stored in the array. This interceptors. Response. ForEach (function pushResponseInterceptors (interceptor) { chain.push(interceptor.fulfilled, interceptor.rejected); }); While (chain.length) {promise = promise.then(chain.shift(), chain.shift())); } // Return onResolved and onRejected promise return promise; };Copy the code
  • Overall process:request(config)= = >dispatchRequest(config)= = >xhrAdapter(config)
  1. rightconfigJudgment and initialization, between the way Axios calls. throughmergeConfigFunction on the incomingconfigandthis.defaultConsolidates the properties of the specified property name in the configuration object, using the configuration in config in preference. Determine the type of request, lowercase if there is, default to noget.
  2. Creates an array to hold the request/response interceptor functionschainIn the middle of the arraySend the requestTo the left of the arrayRequest interceptorFunction (success/failure) on the right side of the arrayResponse interceptorFunction.

  • Why does a chain array need two elements, and why undefined?
  1. As we all know, Axios is an HTTP library based on Promise,PromiseThe object represents an asynchronous operation with three states:pending(In progress),fulfilled(Successfully) andrejected(Failed). Two elements of the chain arrayfulfilledandrejectedOn successdispatchRequestFailure to executeundefined.
  2. Here,undefinedIt doesn’t have any specific meaning, it’s a placeholder that gets executed when the request interceptor fails, okayundefined, promise has exception penetration. When using promise’s then chain invocation, the failed callback can be specified at the end, and any previous operation that fails will be passed to the last failed callback. So in the chainundefinedJumps to the failure callback in response to the interceptor.
  • The implementation is explained in the interceptor below
  1. The function that sends the request isdispatchRequest, sending processdispatchRequest.js->Default. Js getDefaultAdapter in ()->Adapters. Js in Xhr. js or http.js.xhr.jsMaking a request using a traditional XMLHttpRequest object,http.jsThe Node module is usedhttp.
  2. The final promise is then returned by concatenating all request interceptors/request method/response interceptors with the promise’s then().

After the pr Axios. Prototype. The request

  • Take a look at the pr below, and the PR discussion in the AXIos source code. Due to the unexpected failure of the aixOS promise request, the PR reference is the most complete and detailed interpretation of the AXIos source code

After reading, I think: The reason is that when executing the following line of code, after passing the previous interceptor (successfully), when executing the dispatchRequser function for the XHR request, the while loop will continue to run ahead until the request is converted to a promise, which will cause the axios request to be delayed. Response pages are rendered late, affecting bounce rate/conversion rate and user experience. Not everyone utilizes interceptors, and creating commitments only when using them might be a good way to address this problem,

Look at the PR code: Request interception is used for judgment purposes, since the user-provided custom adapter function is not guaranteed to return a Promise. If the user-provided interceptor function is asynchronous, Will through the interceptor. Synchronous parameter Settings synchronousRequestInterceptors to false, the interceptor chain call before no pr. If the user set the interceptor function are synchronous, the synchronousRequestInterceptors default is true (the default is synchronous), no change, then out dispatchRequest function from inside the while loop, to implement the interceptors, Wait for all interceptors to complete and call dispatchRequest for XHR requests. Wait for the request to complete and then respond to interceptors to run.

  while (chain.length) {
    promise = promise.then(chain.shift(), chain.shift());
  }
Copy the code

Simulation of the request

Due to the complexity and difficulty of the modified Axios.prototype.request (Can’t write), but the core chain call remains the same, so I can only simulate the request before the modification.

Function Axios(config){this.config = config} axios.prototype. Request = function(config){ Resolve (config) let Chains = [dispatchRequest, Undefined // then I pay. Let result = promise. Then (Chains [0], chains[1]) return result } // 2. dispatchRequest function dispatchRequest(config){ // ... Return xhrAdapter(config). Then (response =>{//... Return response},error =>{throw error})} // 3. adapter adapter function xhrAdapter(config){ Console. log("xhrAdapter function execution "); Return new Promise((resolve, reject)=>{// Send ajax request let XHR = new XMLHttpRequest() // initialize xhr.open(config.method, Config.url) // Send xhr.send() // Bind the event xhr.onReadyStatechange = ()=>{if(xhr.readyState === 4){if(xhr.status >= 200&& XHR. Status < 300) {/ / successful resolve ({config: config, data: XHR. Response, headers: XHR. The getAllResponseHeaders (), the request: xhr, status: xhr.status, statusText: Xhr.statustext})}else{// reject(new Error(" Request failed, The request of the failure code is "+ XHR. Status))}}}}} let axios = axios. Prototype. Request. Bind (null) / / use axios functions found request, Call axios.prototype. requsert Axios ({method:"GET", url:"http://localhost:3000/posts" }).then(response =>{ console.dir(response); })Copy the code
  • xhrThe request only writes a success return.

Principle of interceptor

  • Axios.prototype.request: interceptorManager.js
function InterceptorManager() { this.handlers = []; } InterceptorManager.prototype.use = function use(fulfilled, rejected, options) { this.handlers.push({ fulfilled: This is a pity, rejected: // PR after the two attributes, the user sets the interceptor synchronous or asynchronous, the default is 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); }}); };Copy the code
Function Axios(instanceConfig) {// Save the specified config as the defaults property this.defaults = instanceConfig; // save the object containing the request/response InterceptorManager as the interceptors attribute this.interceptors = {request: new InterceptorManager(), response: new InterceptorManager() }; }Copy the code
  • interceptorsThere arerequest,responseTwo properties, both of themInterceptorManagerThe instance object has oneheadersArray, the prototype object has oneuseBy means ofuseMethod pushes the success and failure callbacks of the user-defined request interceptor intoheadersArray header, if there are more than one request interceptor, each interceptor before the previous interceptor, similar to first in last out. The response interceptor also passesuseMethod add, but he uses something like first in, first out, and pushes it to the end of the array each time.

The picture is from the author’s video screenshot of Tencent cloud community Pingan8787

  • When the request came to request the interceptor, will remove the top two elements array (success callback callbacks and failure), is the state of processing parameter is the promise of success, implement the success callback, the state is callback is executed failure, we can judge whether the request in the interceptor callback meet the conditions, meet the release, go to the next interceptorsuccessfulThe callback ordispatchRequset, fail to proceed to the next interceptorfailureThe callback orundefined. Successful callback Successfully executes the next successful callback, and failed executes the next failed callback.
  • The response interceptor does the same, but eventually returns the result of the request axios().then() processing.

Flow: Request interceptor 2 => Request interceptor 1 => make an Ajax request => respond to interceptor 1 => respond to interceptor 2 => request callback

Interceptor simulation

Function Axios(config) {this.config = config this.interceptors = {response: new InterceptorManger(), request: new InterceptorManger() } } Axios.prototype.request = function (config) { let promise = Promise.resolve(config) const Chains = [dispatchRequest, undefined] Will request interceptor in the front of the back pressure regulating into chains of enclosing interceptors. Request. Handers. ForEach (item = > {chains. The unshift (item fulfiled, Item.reject)}) // Response interceptor, Interceptors will response back to surge into the back of the chains of enclosing interceptors. Response. Handers. ForEach (item = > {chains. Push (item fulfiled, While (chains. Length >0){promise = promise.then(chains. Shift (), Chains. Shift ()} return promise (){return promise (); reject) => { resolve({ status: 200, statusText: "OK"})})} / / interceptor manager constructor function InterceptorManger () {enclosing handers = []} InterceptorManger. Prototype. Use = function  (fulfiled, reject) { this.handers.push({ fulfiled, Reject})} / / create axios function let the context = new axios () let axios = axios. Prototype. Request. Bind (context) Object.keys(context).foreach (key =>{axios[key] = context[key]} Axios. Interceptors. Request. Use (function one (config) {the console. The log (" request interceptor success - 1 "); Config. params = {a: 100} return config}, function (error) {console.log(" request interceptor failed -1 "); Return Promise. Reject (error)}) axios. Interceptors. Request. Use (function two (config) {the console. The log (" request interceptor success - 2 "); Config. timeout = 2000 return config}, function (error) {console.log(" request interceptor failed -2 "); Return Promise. Reject (error)}) / / set response interceptor axios. Interceptors. Response. Use (function one (response) {the console. The log (" response to the interceptor - 1 "); Return response}, function (error) {console.log(" response interceptor failed -1 "); Return Promise. Reject (error)}) axios. Interceptors. Response. Use (function two (response) {the console. The log (" response interceptor success - 2 "); Return response}, function (error) {console.log(" response interceptor failed -2 "); Return promise.reject (error)}) // Send the request axios({method: "GET", url:"http://localhost:3000/posts" }).then(response => { console.log(response); })Copy the code
  • By default, the callback is successfully executed

Principle of Canceling Requests

  • Requesting cancellation requires passing in a cancelToken attribute in config, which is a cancelToken instance object (axios.cancelToken)
axios({
    method: "GET",
    url: "http://localhost:3000/posts",
    cancelToken: new CancelToken(function (c) {
                        cancel = c
                 })
 })
Copy the code
  • CancelToken constructor in canceltoken.js
Function CancelToken(executor) {if (typeof executor! == 'function') { throw new TypeError('executor must be a function.'); } // Prepare a promise object for the cancellation request and save the resolve function var resolvePromise; this.promise = new Promise(function promiseExecutor(resolve) { resolvePromise = resolve; }); Var token = this; Function cancel(message) {function cancel(message) {// If there is reason in the token, If (token.reason) {// Cancellation has already been requested return; } // Specify token's reason as a Cancel object token.reason = new Cancel(message); // Specify the cancellation promise as success, with the value reason resolvePromise(token.reason); }); }Copy the code
  • xhr.js
/ / if the configuration cancelToken if (config. CancelToken) {/ / callback function specified for interrupt request config. CancelToken. Promise. Then, the function onCanceled(cancel) { if (! request) { return; } // Abort the request request.abort(); // make the requested promise fail reject(cancel); // Clean up request request = null; }); }Copy the code
  • At the heart of the cancellation request is the xhr.abort() method, so you need to know when to call it.
  • The CancelToken constructor adds a Promise property to the CancelToken object instance, which is a Promise object and changes the state of the objectresolveMethod assigned to a variableresolvePromise. The executor function is the callback passed in by the CancelToken instance object of the user-stored CancelToken attribute in Configfunction(c){... }And the function in executor() assigns a Reason attribute (Boolean) to the cancel, CancelToken instance object.
  • indispatchRequest.jscall
function throwIfCancellationRequested(config) { if (config.cancelToken) { config.cancelToken.throwIfRequested(); }}. The module exports = function dispatchRequest (config) {/ * if the request has been canceled, direct throw an exception * / throwIfCancellationRequested (config); . }Copy the code
CancelToken.prototype.throwIfRequested = function throwIfRequested() { if (this.reason) { throw this.reason; }};Copy the code
  • The cancel function is then executed by the user clicking a cancellation request such as a button, and the state of the PROMISE property of the CancelToken object instance is changedxhr.jsIn theconfig.cancelToken.promise.then()The result of executing the first argument – the callback functionrequest.abort()Execute, request cancellation.

Mock request cancellation

  • Before simulating, you need to re-create jSON-server and enter it on the command linejson-server --w db.json -d 2000, let the request delay 2 seconds, otherwise the request success too fast, cancel too late
<! DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8"> <meta http-equiv="X-UA-Compatible" content="IE=edge"> <meta  name="viewport" content="width=device-width, Initial-scale =1.0"> <title>Document</title> </head> <body> <div class="container"> <h2 Class ="page-header">axios </h2> <button class=" BTN btn-primary"> </button> <button class=" BTN" </button> </div> <script> // constructor function Axios(config) {this.config = config} // Prototype request method Axios.prototype.request = function (config) {return dispatchRequest(config) DispatchRequest (config) {return xhrAdapter(config)} // xhrAdapter function xhrAdapter(config) {// Send AJAX request return New Promise((resolve, reject) => {// instantiate the object let XHR = new XMLHttpRequest() // initialize xhr.open(config.method, Xhr.onreadystatechange = function () {if (xhr.readyState === 4) {// Check the result if (xhr.status >= 200 && xhr.status < 300) { resolve({ status: xhr.status, statusText: Xhr.statustext})} else {reject(new Error(" reject "))}}} // Cancel request processing if (config.cancelToken) {// cancel cancelToken Upon the promise of object specifies the success callback config. The cancelToken. Promise. Then (value = > {XHR. Abort () reject (new Error (" request has been cancelled ")})}})} / / Function CancelToken(executor) {var resolvePromise; ResolvePromise = new Promise((resolve) => {resolvePromise = resolve}) ResolvePromise ()})} / / the following is a test let context = new Axios () let Axios = Axios. Prototype. Request. Bind (context) let cancel = null const btns = document.querySelectorAll("button") btns[0].onclick = function () { axios({ method: "GET", url: "http://localhost:3000/posts", cancelToken: new CancelToken(function (c) { cancel = c }) }). then(response => { console.log(response); cencel = null }) } btns[1].onclick = function () { cancel() } </script> </body> </html>Copy the code

conclusion

Axios source I realize deeply, looked at the code line by line, don’t know the words one by one, multiple files, so embarrassing, but calm, really can learn a lot about, not just the source content, also have a plenty of the author all sorts of clever design, such as the design of the interceptor, promise chain framework, using the pressure in the array, Easy to understand. For example, create a Promise object property in a cancel request to determine whether to cancel the request by changing the state of the object. And…

This article explains and simulates axios’ basic functions. If it’s useful to you, I hope you can move your hands and give a thumbs up.