1. Introduction

Hi, I’m Wakawa. This is the sixth in the series on learning the overall architecture of the source code. Overall architecture this word seems to be a bit big, let’s say it is the overall structure of the source code, the main is to learn the overall structure of the code, do not go into other is not the main line of the specific function implementation. In this article, you’ll learn the code for the actual repository.

Learn the overall architecture of the source code series articles as follows:

1. Learn the overall architecture of jQuery source code, and create your own JS class library 2. Learn to use the whole framework of the source code to create their own functional programming library 3. Learning loDASH source code overall architecture, to create their own functional programming class library 4. Learn sentry source code overall architecture, build their own front-end exception monitoring SDK 5. Learn vuEX source code overall architecture, to create their own state management library 6. Learning axiOS source code overall architecture, to create their own request library 7. Learning koA source code overall architecture, a brief analysis of KOA onion model principle and CO principle 8. Learn redux source code overall architecture, in-depth understanding of Redux and its middleware principles

Interested readers can click to read. The next article may be the vue-Router source code.

This article is relatively long, read on the mobile phone, you can directly look at a few pictures in the article. Suggest to like or collect after reading on the computer, according to the article debugging way their own debugging may be easier to absorb and digest.

The article introduces axiOS debugging method in detail. The implementation of axiOS constructors, interceptors and cancellations are introduced in detail. Finally, other request libraries are compared.

The version for this article is V0.19.0. Clone the master branch of the official repository. As of December 14, 2019, the latest commit is 2019-12-09 15:52 ZhaoXC DC4BC49673943E352, fix: Fix ignore set withCredentials false (#2582).

The repository for this article is here at the Axios-Analysis Github repository in Wakawa. Get a star.

If you’re a candidate for a project written using Axios, the interviewer may ask you:

1. Why axios can be used both as a function call and as an object, such as axios({}) and axios.get. 2. Describe the axiOS invocation process. 3. Have you ever used interceptors? How does it work? 4. Is there a cancel function using Axios? How does it work? 5. Why do you support browser send requests and node send requests?

Things like that.

2. Debug axios source code in chrome and vscode

Not long ago, the author in Zhihu answered a question in a year of front-end do not understand the front-end framework source code to do? Recommended some materials, reading is good, you can have a look at the interest. There are four main points:

1. With the help of debugging 2. Search for relevant highly praised articles 3. conclusion

Debugging is important when looking at the source code, so I wrote down the axios source code debugging method in detail to help readers who might not know how to debug.

2.1 Chrome debug browser environment axiOS

Debug method

Axios is packaged with the sourcemap file.

# can clone the author of this warehouse code
git clone https://github.com/lxchuan12/axios-analysis.git
cd axios-analaysis/axios
npm install
npm start
# open [http://localhost:3000](http://localhost:3000)
# Chrome F12 source control panel webpack//. Lib directory, according to the case of the breakpoint debugging
Copy the code

In this paper, through the above example is axios/sandbox/client. HTML to debug.

By the way, I will briefly mention the example of debugging example. Although I wrote this part at the beginning of the article and deleted it later, I still wrote it at the end of the article.

Find the file axios/examples/server.js and modify the code as follows:

server = http.createServer(function (req, res) {
  var url = req.url;
  / / debugging examples
  console.log(url);
  // Process axios itself
  if (/axios\.min\.js$/.test(url)) {
    // The original code is axios.min.js
    // pipeFileToResponse(res, '.. /dist/axios.min.js', 'text/javascript');
    pipeFileToResponse(res, '.. /dist/axios.js'.'text/javascript');
    return;
  }
  // The original code is axios.min.map
  // if (/axios\.min.map$/.test(url)) {
  if (/axios\.map$/.test(url)) {
    // The original code is axios.min.map
    // pipeFileToResponse(res, '.. /dist/axios.min.map', 'text/javascript');
    pipeFileToResponse(res, '.. /dist/axios.map'.'text/javascript');
    return; }}Copy the code
# After the dependencies are installed
# NPM Run Examples cannot be enabled at the same time. The default is 3000 port
Port 5000 can be specified
# npm run examples === node ./examples/server.js
node ./examples/server.js -p 5000
Copy the code

Go to http://localhost:5000 and have fun debugging examples in Chrome.

Axios supports the node environment for sending requests. Let’s see how to debug axios in the node environment using vscode.

2.2 vsCode Debugs axiOS in the Node environment

In the root directory of axios-analysis/, create the following.vscode/launch.json file:

{
    // Use IntelliSense to learn the properties.
    // Hover to see the description of the existing property.
    / / for more information, please visit: https://go.microsoft.com/fwlink/?linkid=830387
    "version": "0.2.0"."configurations": [{"type": "node"."request": "launch"."name": "Launch Program"."program": "${workspaceFolder}/axios/sandbox/client.js"."skipFiles": [
                "<node_internals>/**"]},]}Copy the code

Press F5 to start debugging, according to their own situation, step over (F10), step debugging (F11) breakpoint debugging.

In fact, open source projects generally have a CONTRIBUTING guide, axios/CONTRIBUTING. Md, but I just modified this guide to make sourcemap reference files debugable.

3. What is the structure of AXIOS

git clone https://github.com/lxchuan12/axios-analysis.git
cd axios-analaysis/axios
npm install
npm start
Copy the code

After NPM start, debug directly in Chrome browser. Open http://localhost:3000 and print out axios from the console, which many of you may not have seen.

console.log({axios: axios});
Copy the code

To get an idea of what the structure of Axios looks like, click through the layers.

The author draws a more detailed graph representation.

Looking at the final composition, if you look at the jQuery, underscore, and Lodash source code, you’ll see that it’s actually similar to the Axios source design.

Underscore $, underscore loadsh alias _ For example, how to use jQuery. $(‘ # id), a $. Ajax.

Next, look at the implementation of the specific source code. You can debug with breakpoints.

Breakpoint debugging essentials: the assignment statement can be skipped in one step, to see the return value, and then see in detail. Function execution requires breakpoints to follow, and you can also use comments and context to backtrack what the function did.

4. Axios source initialization

The first step in the source code is to look at package.json. The main entry file is declared.

// package.json
{
  "name": "axios"."version": "0.19.0"."description": "Promise based HTTP client for the browser and node.js"."main": "index.js".// ...
}
Copy the code

Main entry file

// index.js
module.exports = require('./lib/axios');
Copy the code

4.1 lib/axios.jsMaster file

The AXIos.js file has a lot of code. The narrative is divided into three parts.

  1. Part ONE: Introduce some utility functionsutils,AxiosConstructor, default configurationdefaultsAnd so on.

  2. The second part is to generate instance objectsaxios,axios.Axios,axios.createAnd so on.

  3. Part three cancellations the associated API implementation, andall,spread, export, etc.

4.1.1 Part ONE

Introduces some utility functions such as utils, Axios constructors, defaults, and so on.

// Part I:
// lib/axios
// Strict mode
'use strict';
// Introduce the utils object, which has many utility methods.
var utils = require('./utils');
// Introduce the bind method
var bind = require('./helpers/bind');
// The core constructor Axios
var Axios = require('./core/Axios');
// Merge configuration methods
var mergeConfig = require('./core/mergeConfig');
// Import the default configuration
var defaults = require('./defaults');
Copy the code

4.1.2 Part II

Is to generate instance objects axios, axios. axios, axios.create, and so on.

/**
 * Create an instance of Axios
 *
 * @param {Object} defaultConfig The default config for the instance
 * @return {Axios} A new instance of Axios
 */
function createInstance(defaultConfig) {
  // new An instance object generated by Axios
  var context = new Axios(defaultConfig);
  // bind returns a new wrap function,
  // That's why calling axios is calling the Axios.prototype.request function
  var instance = bind(Axios.prototype.request, context);
  // Copy axios.prototype to instance
  // Copy axios.prototype to the instance.
  // That's why there are alias methods like axios.get,
  // And calls the axios.prototype.get alias method.
  utils.extend(instance, Axios.prototype, context);
  // Copy context to instance
  // Copy the context to the intance instance
  // This is why axios.defaults and axios.interceptors can be used
  New Axios().defaults and new Axios().interceptors
  utils.extend(instance, context);
  // Finally return the instance object, as shown in the figure above. Take a closer look at the picture above.
  return instance;
}

// Create the default instance to be exported
// Export to create a default instance
var axios = createInstance(defaults);
// Expose Axios class to allow class inheritance
New axios.axios ()
// But this is not mentioned in the Axios documentation, and we use it less often.
axios.Axios = Axios;

// Factory for creating new instances
// The factory pattern creates a new instance. The user can customize some parameters
axios.create = function create(instanceConfig) {
  return createInstance(mergeConfig(axios.defaults, instanceConfig));
};
Copy the code

Here’s a brief description of the factory model. Axios.create, where the user does not need to know how the internals are implemented. Take life as an example, we buy mobile phones, do not need to know how the phone is made, is the factory model. Look at the second part, which covers several utility functions, such as bind, extend. These tools are described next.

4.1.3 Bind of tool methods

axios/lib/helpers/bind.js

'use strict';
// Return a new function wrap
module.exports = function bind(fn, thisArg) {
  return function wrap() {
    var args = new Array(arguments.length);
    for (var i = 0; i < args.length; i++) {
      args[i] = arguments[i];
    }
    // Put the argument object in the array args
    return fn.apply(thisArg, args);
  };
};
Copy the code

Pass two arguments to the function and thisArg point. Generate an array of arguments. The final call returns the argument structure. Now that Apply supports array-like objects like Arguments, you don’t need to manually convert the array. So why did the author convert the array, for performance? Not at the time? Or does the author not know? We don’t know. Readers are welcome to let me know in the comments section.

For those less familiar with apply, Call, and Bind, see my other interviewers’ questions series. Interviewer: can you simulate the JS bind method

For example

function fn(){
  console.log.apply(console.arguments);
}
fn(1.2.3.4.5.6.'if the sichuan');
// 1 2 3 4 5 6
Copy the code

4.1.4 Tool method utils.extend

axios/lib/utils.js

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; }});return a;
}
Copy the code

If it is a function, it will be called with bind.

4.1.5 Tool method utils. ForEach

axios/lib/utils.js

Iterate over groups and objects. Design patterns are called iterator patterns. Many source codes have traversal functions like this. For example, you are familiar with jQuery $. Each.

/ * * *@param {Object|Array} obj The object to iterate
 * @param {Function} fn The callback to invoke for each item
 */
function forEach(obj, fn) {
  // Don't bother if no value provided
  // Null and undefined are returned
  if (obj === null || typeof obj === 'undefined') {
    return;
  }

  // Force an array if not already something iterable
  // If it's not an object, put it in an array.
  if (typeofobj ! = ='object') {
    /*eslint no-param-reassign:0*/
    obj = [obj];
  }

  // If it is an array, use the for loop and call fn. The parameters are similar to the first three parameters of array.prototype. forEach.
  if (isArray(obj)) {
    // Iterate over array values
    for (var i = 0, l = obj.length; i < l; i++) {
      fn.call(null, obj[i], i, obj); }}else {
    // Iterate over object keys
    For in traverses the object, but for in traverses traversable properties on the prototype chain.
    // Use hasOwnProperty to filter properties.
    // You can also use object. keys to iterate. It does not iterate over the iterable properties on the prototype chain.
    for (var key in obj) {
      if (Object.prototype.hasOwnProperty.call(obj, key)) {
        fn.call(null, obj[key], key, obj); }}}}Copy the code

If you’re not familiar with the Object related API, check out my previous article. JavaScript object all API parsing

4.1.6 Part III

Cancel the relevant API implementation, as well as all, SPREAD, export, etc.

// Expose Cancel & CancelToken
// Export Cancel and CancelToken
axios.Cancel = require('./cancel/Cancel');
axios.CancelToken = require('./cancel/CancelToken');
axios.isCancel = require('./cancel/isCancel');

// Expose all/spread
// Export the all and Spread apis
axios.all = function all(promises) {
  return Promise.all(promises);
};
axios.spread = require('./helpers/spread');

module.exports = axios;

// Allow use of default import syntax in TypeScript
// It can be introduced in the following ways
// import axios from 'axios';
module.exports.default = axios;
Copy the code

Spread is introduced here. The cancelled API will not be analyzed for the time being, and will be analyzed in detail later.

Let’s say you have a need.

function f(x, y, z) {}
var args = [1.2.3];
f.apply(null, args);
Copy the code

So you can use spread. Usage:

axios.spread(function(x, y, z) {}) ([1.2.3]);
Copy the code

Implementation is also relatively simple. Source code implementation:

/ * * *@param {Function} callback
 * @returns {Function}* /
module.exports = function spread(callback) {
  return function wrap(arr) {
    return callback.apply(null, arr);
  };
};
Copy the code

Var context = new Axios(defaultConfig); Next, we introduce the core constructor Axios.

4.2 Core constructor Axios

axios/lib/core/Axios.js

The constructor Axios.

function Axios(instanceConfig) {
  // Default parameters
  this.defaults = instanceConfig;
  // Interceptor Requests and responds to interceptors
  this.interceptors = {
    request: new InterceptorManager(),
    response: new InterceptorManager()
  };
}
Copy the code
Axios.prototype.request = function(config){
  // Omit. This is the core method
  // code ...
  var promise = Promise.resolve(config);
  // code ...
  return promise;
}
// This is the function that gets the Uri, omitted here
Axios.prototype.getUri = function(){}
// Provide some aliases for the requested methods
// Provide aliases for supported request methods
// Traversal execution
// That's why we can call axios.get and call axios.prototype.request
// This is also shown in the Axios diagram above.
utils.forEach(['delete'.'get'.'head'.'options'].function forEachMethodNoData(method) {
  /*eslint func-names:0*/
  Axios.prototype[method] = function(url, config) {
    return this.request(utils.merge(config || {}, {
      method: method,
      url: url
    }));
  };
});

utils.forEach(['post'.'put'.'patch'].function forEachMethodWithData(method) {
  /*eslint func-names:0*/
  Axios.prototype[method] = function(url, data, config) {
    return this.request(utils.merge(config || {}, {
      method: method,
      url: url,
      data: data
    }));
  };
});

module.exports = Axios;
Copy the code

Now let’s look at the interceptor part.

4.3 Interceptor Management constructor: InterceptorManager

Pre-request interception, and post-request interception. It is used in the Axios.prototype.request function. How to intercept is described in detail with examples.

Axios Github repositories interceptor documents

How to Use:

// Add a request interceptor
// Add a pre-request interceptor
axios.interceptors.request.use(function (config) {
  // Do something before request is sent
  return config;
}, function (error) {
  // Do something with request error
  return Promise.reject(error);
});

// Add a response interceptor
// Add a post request interceptor
axios.interceptors.response.use(function (response) {
  // Any status code that lie within the range of 2xx cause this function to trigger
  // Do something with response data
  return response;
}, function (error) {
  // Any status codes that falls outside the range of 2xx cause this function to trigger
  // Do something with response error
  return Promise.reject(error);
});
Copy the code

If you want an interceptor, you can use the eject method.

const myInterceptor = axios.interceptors.request.use(function () {/ *... * /});
axios.interceptors.request.eject(myInterceptor);
Copy the code

Interceptors can also be added to custom instances.

const instance = axios.create();
instance.interceptors.request.use(function () {/ *... * /});
Copy the code

Source code implementation:

The handles constructor is used to store the interceptor functions.

function InterceptorManager() {
  this.handlers = [];
}
Copy the code

Next, three methods are declared: use, remove, and Iterate.

4.3.1 InterceptorManager. Prototype. Use to use

Fulfilled: function(){}, rejected: function(){}} Returns the numeric ID used to remove the interceptor.

/ * * *@param {Function} fulfilled The function to handle `then` for a `Promise`
 * @param {Function} rejected The function to handle `reject` for a `Promise`
 *
 * @return {Number} The ID is returned to remove the */ with eject
InterceptorManager.prototype.use = function use(fulfilled, rejected) {
  this.handlers.push({
    fulfilled: fulfilled,
    rejected: rejected
  });
  return this.handlers.length - 1;
};
Copy the code

4.3.2 InterceptorManager. Prototype. Eject removed

Remove the interceptor based on the ID returned by use.

/ * * *@param {Number} id The ID that was returned by `use`
 */
InterceptorManager.prototype.eject = function eject(id) {
  if (this.handlers[id]) {
    this.handlers[id] = null; }};Copy the code

Similar to the setTimeout and setInterval timers, the return value is an ID. Use clearTimeout and clearInterval to clear timers.

The timer callback function is passable. The return value timer is a number
var timer = setInterval((name) = > {
  console.log(name);
}, 1000.'if the sichuan');
console.log(timer); / / digital ID
// Wait on the console to enter the execute sentence, the timer is cleared
clearInterval(timer);
Copy the code

4.3.3 InterceptorManager. Prototype. ForEach traversal

The removal effect is achieved by iterating through all interceptors, passing in a callback (each of the interceptor functions as an argument). The removed item is null, so it will not be executed.

/ * * *@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

5. Examples

Narrative of commissioning run above NPM start is to use axios sandbox/client. The path of the HTML file as an example, the reader can debug.

Here is a snippet of code from this file.

axios(options)
.then(function (res) {
  response.innerHTML = JSON.stringify(res.data, null.2);
})
.catch(function (res) {
  response.innerHTML = JSON.stringify(res.data, null.2);
});
Copy the code

5.1 Look at the call stack flow first

If you don’t want to debug every step of the way, there’s an ingenious way. Know that AXIos uses XMLHttpRequest. You can search in the project: new XMLHttpRequest. Locating the file axios/lib/adapters/XHR js in this statement var request = new XMLHttpRequest (); Chrome browser to hit a breakpoint debugging, and then according to the call stack to see specific functions and other implementation.

Call Stack

dispatchXhrRequest (xhr.js:19)
xhrAdapter (xhr.js:12)
dispatchRequest (dispatchRequest.js:60)
Promise.then (async)
request (Axios.js:54)
wrap (bind.js:10)
submit.onclick ((index):138)
Copy the code

Briefly describe the process:

  1. Send RequestClick on the buttonsubmit.onclick

  2. callaxiosFunctions are actually callsAxios.prototype.requestFunction, and this function usesbindThe returned one namedwrapThe function.

  3. callAxios.prototype.request

  4. (execute the request interceptor if there is a request interceptor), and execute the request interceptor in the middledispatchRequestmethods

  5. dispatchRequestAfter the calladapter (xhrAdapter)

  6. The last callPromiseThe function indispatchXhrRequest(The response interceptor is called at the end if there is a response interceptor)

If you look closely at the AXIOS structure diagram at the beginning of this article, you get an idea of this process.

Next, look at the implementation of axios.prototype.request.

5.2 Axios.prototype.request Request core method

This function is the core function. I mainly did the following:

1. If the first argument is a string, set the URL to support axios(‘example/url’, [, config]) and axios({}). 2. Merge the default parameters with the parameters passed by the user. 3. The user-set request and response interceptors, the dispatchRequest that sends the request, are formed into a Promise chain, and the Promise instance is returned.

This ensures that the interceptor executes before the request, then sends the request, and then responds to the interceptor. <br> That is why 'then', 'catch' methods are still available. <br>Copy the code
Axios.prototype.request = function request(config) {
  /*eslint no-param-reassign:0*/
  // Allow for axios('example/url'[, config]) a la fetch API
  // Axios ('example/url', [, config])
  // The config parameter can be omitted
  if (typeof config === 'string') {
    config = arguments[1) | | {}; config.url =arguments[0];
  } else {
    config = config || {};
  }

  // Merge the default parameters with the parameters passed by the user
  config = mergeConfig(this.defaults, config);

  // Set config.method
  // Set the request 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';
  }
  // Hook up interceptors middleware
  // The part that makes up the 'Promise' chain will be broken down later
};
Copy the code

5.2.1 compositionPromiseChain, returnPromiseThe instance

This part: the user-set request and response interceptors, along with the dispatchRequest to send the request, make up the Promise chain. This ensures that the interceptor executes before the request, then sends the request, and then responds to the interceptor

The interceptor executes the request before the request, then sends the request, and then executes the interceptor in response <br>, which is why the 'then', 'catch' methods are still available. <br>Copy the code

If you’re not familiar with Promise, I recommend Reading Mr. Ruan’s book, introduction to ES6 Standards. ES6 Promise-Resolve and JavaScript Promise

  // Make a 'Promise' chain
  // Hook up interceptors middleware
  // Put the "dispatchRequest" and "undefined" in an array for XHR requests
  var chain = [dispatchRequest, undefined];
  // Create a Promise instance
  var promise = Promise.resolve(config);

 // Pass the user set request interceptors to the array chain
  this.interceptors.request.forEach(function unshiftRequestInterceptors(interceptor) {
    chain.unshift(interceptor.fulfilled, interceptor.rejected);
  });

 // Iterate over the array of response interceptors set by the user to put behind the chain
  this.interceptors.response.forEach(function pushResponseInterceptors(interceptor) {
    chain.push(interceptor.fulfilled, interceptor.rejected);
  });

 // Iterate over the chain array until the chain. Length is 0
  while (chain.length) {
    // Put the two arguments in the then.
    promise = promise.then(chain.shift(), chain.shift());
  }

  return promise;
Copy the code
var promise = Promise.resolve(config);
Copy the code

Explain that. This is used to generate Promise instances.

var promise = Promise.resolve({name: 'if the sichuan'})
/ / equivalent to the
// new Promise(resolve => resolve({name: 'ruokawa '}))
promise.then(function (config){
  console.log(config)
});
// {name: "{name} "}
Copy the code

Also explain what happens next: promise.reject (error); :

Promise.reject(error);
Copy the code
var promise = Promise.reject({name: 'if the sichuan'})
/ / equivalent to the
// New Promise(reject => reject({name: 'wakawa '}))

// promise.then(null, function (config){
// console.log(config)
// });
/ / equivalent to the
promise.catch(function (config){
  console.log(config)
});
// {name: "{name} "}
Copy the code

Let’s use an example to understand this code. Unfortunately, there are no examples of interceptors in the Example folder. In example, I added an example of an interceptor on top of example/get. Debugging axios/examples/interceptors, facilitate readers.

node ./examples/server.js -p 5000
Copy the code

promise = promise.then(chain.shift(), chain.shift()); Put a breakpoint on this code.

You get a picture that looks something like this.

Pay particular attention to the chain array in local on the right. It’s going to look something like this.

var chain = [
  'Request successfully intercepted 2'.'Request failed interception 2'.'Request successfully intercepted 1'.'Request failed intercept 1',  
  dispatch,  undefined.'Response intercepted 1 successfully'.'Response failed intercept 1'.'Response intercepted successfully 2'.'Response failure intercept 2',]Copy the code

This code is relatively convoluted. This will generate code similar to the following, calling the dispatchRequest method.

// Config is a combination of user configurations and default configurations
var promise = Promise.resolve(config);
promise.then('Request successfully intercepted 2'.'Request failed interception 2')
.then('Request successfully intercepted 1'.'Request failed intercept 1')
.then(dispatchRequest, undefined)
.then('Response intercepted 1 successfully'.'Response failed intercept 1')
.then('Response intercepted successfully 2'.'Response failure intercept 2')

.then('User-written business processing functions')
.catch('User-written error reporting business handler');
Copy the code

The promise.prototype. then method takes the promise.prototype. then callback for the promised state and the (optional) rejected state. So it comes in pairs. The promise.prototype. catch method is an alias for. Then (null, rejection) or. Then (undefined, rejection), which specifies the callback function when an error occurs. The then method returns a new Promise instance (note, not the original Promise instance). So you can write chained, where the THEN method is followed by another THEN method.

Taking the above example into more detail, the code looks like this.

var promise = Promise.resolve(config);
// promise.then(' request succeeds intercept 2', 'request fails intercept 2')
promise.then(function requestSuccess2(config) {
  console.log('------request------success------2');
  return config;
}, function requestError2(error) {
  console.log('------response------error------2');
  return Promise.reject(error);
})

//.then(' request succeeds intercept 1', 'request fails intercept 1')
.then(function requestSuccess1(config) {
  console.log('------request------success------1');
  return config;
}, function requestError1(error) {
  console.log('------response------error------1');
  return Promise.reject(error);
})

// .then(dispatchRequest, undefined)
.then( function dispatchRequest(config) {
  Adapter = function xhrAdapter(config) {return new Promise(function dispatchXhrRequest(resolve,  reject) {}) } **/
  return adapter(config).then(function onAdapterResolution(response) {
    // Omit the code...
    return response;
  }, function onAdapterRejection(reason) {
    // Omit the code...
    return Promise.reject(reason);
  });
}, undefined)

//.then(' response succeeds intercept 1', 'Response fails intercept 1')
.then(function responseSuccess1(response) {
  console.log('------response------success------1');
  return response;
}, function responseError1(error) {
  console.log('------response------error------1');
  return Promise.reject(error);
})

//.then(' response succeeds intercept 2', 'Response fails intercept 2')
.then(function responseSuccess2(response) {
  console.log('------response------success------2');
  return response;
}, function responseError2(error) {
  console.log('------response------error------2');
  return Promise.reject(error);
})

//.then(' user-written business handler ')
//.catch(' user write error business handler ');
.then(function (response) {
  console.log('Hahaha, finally got the data.', response);
})
.catch(function (err) {
  console.log('Oh, what's wrong?', err);
});
Copy the code

If you look closely at the Promise chain call, the code is similar. The last argument returned by the THEN method is the first argument of the next THEN method. All errors are returned by catch. Reject (error), which makes it easier for users to catch the error when they catch it.

Here’s an example:

var p1 = new Promise((resolve, reject) = > {
 reject(new Error({name: 'if the sichuan'}));
});

p1.catch(err= > {
    console.log(res, 'err');
    return Promise.reject(err)
})
.catch(err= > {
 console.log(err, 'err1');
})
.catch(err= > {
 console.log(err, 'err2');
});
Copy the code

Err2 does not catch them, that is, it does not execute them, but if both return promise.reject (err), it can catch them.

Let me draw a picture of a Promise chain call.

Request and response interceptors can write promises. 2. If multiple request responders are configured, the first set request responder is executed first. 3. If multiple response interceptors are configured, the one set first is executed first.

DispatchRequest (config) Where the config is returned by the interceptor on a successful request. Now let’s look at the dispatchRequest function.

5.3 dispatchRequest Final dispatch requests

This function does a few things:

1. If it has been canceled, throw the cause to error, making the Promise go to rejected. 2. Ensure that config.header exists. 3. Convert data using user-set and default request converters. 4. Level the config.header. 5. Delete some config.headers. 6. Return a Promise instance after adapter (Promise instance) is executed. The returned result is passed to the response interceptor for processing.

'use strict';
// utils
var utils = require('. /.. /utils');
// Convert the data
var transformData = require('./transformData');
// Cancel the status
var isCancel = require('.. /cancel/isCancel');
// Default parameters
var defaults = require('.. /defaults');

/** * throw the error reason, make 'Promise' to 'rejected' */
function throwIfCancellationRequested(config) {
  if(config.cancelToken) { config.cancelToken.throwIfRequested(); }}/**
 * Dispatch a request to the server using the configured adapter.
 *
 * @param {object} config The config that is to be used for the request
 * @returns {Promise} The Promise to be fulfilled
 */
module.exports = function dispatchRequest(config) {
  // Cancel correlation
  throwIfCancellationRequested(config);

  // Ensure headers exist
  // Make sure headers exists
  config.headers = config.headers || {};

  // Transform request data
  // Convert the requested data
  config.data = transformData(
    config.data,
    config.headers,
    config.transformRequest
  );

  // Flatten headers
  / / flat headers
  config.headers = utils.merge(
    config.headers.common || {},
    config.headers[config.method] || {},
    config.headers || {}
  );

  // The following methods delete headers
  utils.forEach(
    ['delete'.'get'.'head'.'post'.'put'.'patch'.'common'].function cleanHeaderConfig(method) {
      deleteconfig.headers[method]; });// The adapter is broken down below
};
Copy the code

5.3.1 transformData of dispatchRequest

In the previous code there is a function transformData, which is explained here. So what you’re doing is you’re going through the array of functions that you’re passing and you’re operating on the data, and you’re returning the data.

Axios. Defaults. TransformResponse default has a function in the array, so use the concat link custom function.

Use:

File path axios/examples/transform-response/index.html

This code simply converts a string in time format into a time object, which can be called directly by methods like getMonth.

var ISO_8601 = /(\d{4}-\d{2}-\d{2})T(\d{2}:\d{2}:\d{2})Z/;
function formatDate(d) {
  return (d.getMonth() + 1) + '/' + d.getDate() + '/' + d.getFullYear();
}

axios.get('https://api.github.com/users/mzabriskie', {
  transformResponse: axios.defaults.transformResponse.concat(function (data, headers) {
    Object.keys(data).forEach(function (k) {
      if (ISO_8601.test(data[k])) {
        data[k] = new Date(Date.parse(data[k])); }});return data;
  })
})
.then(function (res) {
  document.getElementById('created').innerHTML = formatDate(res.data.created_at);
});
Copy the code

Source:

You go through the array, you call the data and headers call function in the array.

module.exports = function transformData(data, headers, fns) {
  /*eslint no-param-reassign:0*/
  utils.forEach(fns, function transform(fn) {
    data = fn(data, headers);
  });

  return data;
};
Copy the code

5.3.2 Adapter execution of dispatchRequest

Adapters, called adapter patterns in design patterns. Let me give you a simple example from life, so you can understand it.

We used to use mobile phone headphone jack is a round hole, and now is basically the headphone jack and charging interface into one. Unified as TypeC.

In this case, we need a typeC round hole to convert interface, this is the adapter.

  // Adapter
  var adapter = config.adapter || defaults.adapter;

  return adapter(config).then(function onAdapterResolution(response) {
    throwIfCancellationRequested(config);

    // Transform response data
    // Convert the response data
    response.data = transformData(
      response.data,
      response.headers,
      config.transformResponse
    );

    return response;
  }, function onAdapterRejection(reason) {
    if(! isCancel(reason)) {// Cancel correlation
      throwIfCancellationRequested(config);

      // Transform response data
      // Convert the response data
      if(reason && reason.response) { reason.response.data = transformData( reason.response.data, reason.response.headers, config.transformResponse ); }}return Promise.reject(reason);
  });
Copy the code

Let’s look at the specific adapter.

5.4 Adapter Actually sends a request

var adapter = config.adapter || defaults.adapter;
Copy the code

Look at the above adapter, you can know that support user customization. For example, you can write an Adapter through the wechat small program WX. Request in accordance with the requirements. Let’s move on to defaults.ddapter. File path: axios/lib/defaults.js

Based on the current environment, XHR is introduced in the browser environment, and HTTP is introduced in the Node environment. This is also seen in the Sentry-javascript source code.

function getDefaultAdapter() {
  var adapter;
  // Based on XMLHttpRequest
  if (typeofXMLHttpRequest ! = ='undefined') {
    // For browsers use XHR adapter
    adapter = require('./adapters/xhr');
    // Judge by process
  } else if (typeofprocess ! = ='undefined' && Object.prototype.toString.call(process) === '[object process]') {
    // For node use HTTP adapter
    adapter = require('./adapters/http');
  }
  return adapter;
}
var defaults = {
  adapter: getDefaultAdapter(),
  // ...
};
Copy the code

xhr

Next up is the familiar XMLHttpRequest object.

If you are not familiar with it, refer to the XMLHttpRequest MDN documentation.

Onabort is a request cancellation event, and withCredentials is a Boolean value that specifies whether cross-domain access-control requests should carry authorization information, such as cookies or authorization headers.

This code has been deleted. For details, you can see Wakawa’s Axios-Analysis warehouse, or you can clone my Axios-Analysis warehouse for specific analysis during debugging.

module.exports = function xhrAdapter(config) {
  return new Promise(function dispatchXhrRequest(resolve, reject) {
    // This code has been deleted
    var request = new XMLHttpRequest();
    request.open()
    request.timeout = config.timeout;
    // Listen for state changes
    request.onreadystatechange = function handleLoad() {
      if(! request || request.readyState ! = =4) {
        return;
      }
      // ...
    }
    / / cancel
    request.onabort = function(){};
    / / error
    request.onerror = function(){};
    / / timeout
    request.ontimeout = function(){};
    // Use cookies across domains
    // A Boolean value that specifies whether cross-domain access-control requests should carry authorization information, such as cookies or authorization headers.
    // Add withCredentials to request if needed
    if(! utils.isUndefined(config.withCredentials)) { request.withCredentials = !! config.withCredentials; }// Upload and download progress related
    // 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);
    }

    // Send the request
    // Send the request
    request.send(requestData);
  });
}
Copy the code

In fact, now fetch support is very good, Ali open source UMI-Request request library, is to use fetch encapsulated, rather than using XMLHttpRequest. At the end of this article, I’ll outline the difference between UMI-Request and Axios.

http

HTTP will not be covered here, but interested readers can check out the Axios-Analysis repository in Wakawa.

module.exports = function httpAdapter(config) {
  return new Promise(function dispatchHttpRequest(resolvePromise, rejectPromise) {}); };Copy the code

The above dispatchRequest has cancellation module, which I think is important, so I will explain it in detail at the end:

5.5 dispatchRequest cancellation module

The request can be cancelled using the Cancel token.

The AXIos Cancel Token API is a Promise cancellation proposal based on cancellation.

The axios cancel token API is based on the withdrawn cancelable promises proposal.

Cancellation axios document

The document describes two ways of using it in detail.

Unfortunately, there are no cancellations in the Example folder. I have added an example of cancellation to example/get in example. Axios/Examples/Cancel for easy reader debugging.

node ./examples/server.js -p 5000
Copy the code

The interceptor module in request and the cancellation module in Dispatch are relatively complex and can be digested with more debugging.

const CancelToken = axios.CancelToken;
const source = CancelToken.source();

axios.get('/get/server', {
  cancelToken: source.token
}).catch(function (err) {
  if (axios.isCancel(err)) {
    console.log('Request canceled', err.message);
  } else {
    // handle error}});// cancel the request (the message parameter is optional)
// Cancel the function.
source.cancel('Oh, I got canceled by Wakawa.');
Copy the code

5.5.1 Code example of the Cancel Request module

The combined source code cancellation process looks something like this. This section is put in the code in axios/examples/cancel-token/index.html.

The config.cancelToken parameter triggers source-cancel (‘ Oops, I was cancelled by Wakawa ‘); It was created.

// source.cancel(' Oh, I was canceled by Wakawa ');
CancelToken instance is generated when you click cancel.
// Click cancel, will generate the cause, read this section after the source code, may be easy to understand.
var config = {
  name: 'if the sichuan'.// This is simplified
  cancelToken: {
        promise: new Promise(function(resolve){
            resolve({ message: 'Oh, I got canceled by Wakawa.'})}),reason: { message: 'Oh, I got canceled by Wakawa.'}}};// Cancel the exception-throwing method
function throwIfCancellationRequested(config){
  // Execute this sentence in case of cancellation
  if(config.cancelToken){
    // Here the source code is easy to execute, I change to concrete code
    // config.cancelToken.throwIfRequested();
    // if (this.reason) {
    // throw this.reason;
    / /}
    if(config.cancelToken.reason){
        throwconfig.cancelToken.reason; }}}function dispatchRequest(config){
  // It is possible that the execution was cancelled at this point, so the thrown error will be caught by err2
  throwIfCancellationRequested(config);
  // Adapter XHR adapter
  return new Promise((resovle, reject) = > {
    var request = new XMLHttpRequest();
    console.log('request', request);
    if (config.cancelToken) {
        // Handle cancellation
        config.cancelToken.promise.then(function onCanceled(cancel) {
            if(! request) {return;
            }

            request.abort();
            reject(cancel);
            // Clean up request
            request = null;
        });
    }
  })
  .then(function(res){
    // If this is not the case, cancel it
    throwIfCancellationRequested(config);
    console.log('res', res);
    return res;
  })
  .catch(function(reason){
    // If this is not the case, cancel it
    throwIfCancellationRequested(config);
    console.log('reason', reason);
    return Promise.reject(reason);
  });
}

var promise = Promise.resolve(config);

// This is true if no interceptors are set
promise
.then(dispatchRequest, undefined)
// User defined then and catch
.then(function(res){
  console.log('res1', res);
  return res;
})
.catch(function(err){
  console.log('err2', err);
  return Promise.reject(err);
});
// err2 {message: "Oops, I was canceled by Wakawa "}
Copy the code

5.5.2 Next look at the source code for the cancel module

See how to generate a config.cancelToken.

File path:

axios/lib/cancel/CancelToken.js

const CancelToken = axios.CancelToken;
const source = CancelToken.source();
source.cancel('Oh, I got canceled by Wakawa.');
Copy the code

The example shows the implementation of CancelToken. Source,

CancelToken.source = function source() {
  var cancel;
  var token = new CancelToken(function executor(c) {
    cancel = c;
  });
  // token
  return {
    token: token,
    cancel: cancel
  };
};
Copy the code

The approximate structure of the source after execution looks like this.

{
    token: {
    promise: new Promise(function(resolve){
      resolve({ message: 'Oh, I got canceled by Wakawa.'})}),reason: { message: 'Oh, I got canceled by Wakawa.'}},cancel: function cancel(message) {
    if (token.reason) {
      // Cancellation has already been requested
      // Cancelled
      return;
    }
    token.reason = {message: 'Oh, I got canceled by Wakawa.'}; }}Copy the code

Next look at new CancelToken

// CancelToken
CancelToken CancelToken CancelToken CancelToken CancelToken CancelToken
function CancelToken(executor) {
  if (typeofexecutor ! = ='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
      // Cancelled
      return;
    }

    token.reason = new Cancel(message);
    resolvePromise(token.reason);
  });
}

module.exports = CancelToken;
Copy the code

This is used in the adapter that sends the request.

// xhr
if (config.cancelToken) {
  // Handle cancellation
  config.cancelToken.promise.then(function onCanceled(cancel) {
    if(! request) {return;
    }

    request.abort();
    reject(cancel);
    // Clean up request
    request = null;
  });
}
Copy the code

DispatchRequest throwIfCancellationRequested in the concrete implementation: throw an exception is thrown.

// Throw an exception function
function throwIfCancellationRequested(config) {
  if(config.cancelToken) { config.cancelToken.throwIfRequested(); }}// Throw exception user {message: 'Oops, I was canceled by Wakawa'}
CancelToken.prototype.throwIfRequested = function throwIfRequested() {
  if (this.reason) {
    throw this.reason; }};Copy the code

Cancel the process call stack

1.source.cancel()

2.resolvePromise(token.reason);

3.config.cancelToken.promise.then(function onCanceled(cancel) {})

Finally, go to request.abort(); “reject(cancel);

So that’s the end of the cancellation process. If the cancelToken is rejected, the user will receive a message {message: ‘user-set cancelToken ‘}.

This is basically the end of the article.

If you can read to the end, you have surpassed many others

Axios is a great request library, but it certainly doesn’t meet every developer’s needs. Let’s look at the other libraries and see what specific needs other developers have.

6. Compare other request libraries

6.1 KoAjax

FCC Chengdu community leader water song open source KoAJAX.

How to run a tech conference using Open Source Software? The following is an excerpt from the article.

The most commonly used HTTP request library in China is axios, right? Although its Interceptor API is.use(), it is completely different from the middleware pattern used by Node.js frameworks such as Express and Koa. Jquery.ajaxprefilter () and dataFilter() are not much better than jQuery. The upload and download progress is simpler than jquery.deferred (), with only two specialized callback options. So, it still has to remember specific apis for specific requirements, which is not concise enough.

Fortunately, in the process of studying how to implement a KOA-like middleware engine with ES 2018 asynchronous iterators, Aqua has made a more practical application — KoAJAX. Its entire execution is based on KOA-style middleware, and it is itself a middleware call stack. Other than the.get(),.post(),.put(),.delete() shortcuts commonly used in RESTful apis, developers need only remember.use() and next(). The rest is the STANDARD ES syntax and TS type derivation.

6.2 Umi-request Open source request library of Alibaba

Umi – request making warehouse

Umi-request is different from FETCH and Axios.

I have to say that UMI-Request is really powerful, and interested readers can read the source code below.

On the basis of axiOS, umI-Request source code should not be difficult to understand.

For example, the UMI-Request cancellation module code is almost identical to axios.

7. To summarize

This article describes axiOS debugging methods in detail. The implementation of axiOS constructors, interceptors and cancellations are introduced in detail. Finally, other request libraries are compared.

Finally, let me draw a picture to summarize the general flow of Axios.

Answer the following question at the beginning of the passage:

If you’re a candidate for a project written using Axios, the interviewer may ask you:

1. Why axios can be used both as a function call and as an object, such as axios({}) and axios.get.

A: Axios is essentially a function. It assigns some alias methods, such as get and POST, to be called. The final call is axios.prototype.request.

2. Describe the axiOS invocation process.

A: The axios.prototype. request method is actually called, and it returns a promise chained call. The actual request is dispatched in the dispatchRequest.

3. Have you ever used interceptors? How does it work?

A: used with axios. Interceptors. Request. Use add interceptors function request success and failure, with axios. Interceptors. Response. Use add interceptors function response to success and failure. In Axios. Prototype. The request of function promise chain called, Interceptors. Protype. ForEach traversal request and response the interceptor is added to the real request dispatchRequest at both ends, In this way, interception before request and interception after response are achieved. Interceptor also support with Interceptors. Protype. Eject method is removed.

4. Is there a cancel function using Axios? How does it work?

A: Yes, it is cancelled by passing the config cancelToken. The cancelToken is passed. DispatchRequest throws an error. Request.abort () cancelToken cancelToken cancelToken is passed.

5. Why do you support browser send requests and node send requests?

A: The axios.defaults.Adapter default configuration determines whether the environment is browser or node, and uses the corresponding adapter. The adapter supports customization.

To answer the interviewer’s question, readers can also organize the language according to their own understanding, the author’s answer is just for reference.

The Axios source code is relatively small, packaged in over a thousand lines, easy to read and well worth learning.

Clone Wakawa axios-Analysis Github warehouse, according to the method in the article to debug their own, more impressive.

Build a Promise chain based on a Promise, cleverly set up the request interceptor, send the request, and try the response interceptor.

The interceptor module in request and the cancellation module in Dispatch are relatively complex and can be digested with more debugging.

When axios is a function, it calls the Axios.prototype.request function. When it is a function, it calls the object, which has request methods such as GET and POST, and finally calls the Axios.prototype.request function.

There are a number of design patterns used in axios source code. Such as factory, iterator, adapter, and so on. If you want to systematically learn design patterns, JavaScript design patterns and development practices with a score of 9.1 on Douban are generally recommended

If the reader finds something wrong or can be improved, or if there is anything that is not clear, feel free to comment. In addition, I feel that I wrote well, and have some help for you. I can like it, comment on it, and forward it to share. It is also a kind of support for the author.

Recommended reading

Official Axios Github repository

Before writing the article, I searched the following articles and read extensively. Be interested in taking a look at the following articles in comparison, based on code debugging, which also looks fast.

Always feel more search a few articles to see, more useful to their learning knowledge. There’s a term called thematic reading. It basically means a series of readings on a topic.

@Nikunikusan: Axios source code parsing @Mr. Thief _ronffy: Deep analysis of Axios source code – AJAX new king parsing Axios source code line by line Axios refactoring with TypeScript

Another series by the author

How can you simulate the JS call and apply methods? How can you simulate the bind method? How can you simulate the new operator

about

Author: often with the name of Ruochuan mixed in rivers and lakes. The front road lovers | | PPT know little, only good study. Welcome to follow ~ segmentfault front view column, welcome to follow ~ zhihu front view column, welcome to follow ~ github blog, related source code and resources are put here, seek a star^_^~

Welcome to add wechat communication wechat public account

May be more interesting wechat public number, long press scan code concern. Welcome to add the author’s wechat ruochuan12 (specify the source, basic users do not refuse), pull you into [front view communication group], long-term communication and learning ~