I. Introduction to interceptors

Take a look at the official documentation for interceptors:

You can intercept requests or responses before they are handled by then or catch.

That is, we can intercept requests and responses before a promise (resolve) is returned by Axios. Here is the sample code for the document:

// Add a 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 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); }); // If you need to remove an interceptor later you can. const myInterceptor = axios.interceptors.request.use(function () {/ *... * /}); axios.interceptors.request.eject(myInterceptor);Copy the code

There are some features that the sample code doesn’t tell us, so let’s go to Github and take a look at the detailed interceptor documentation. After that, I made a summary of these features:

  1. AxiosRequest and response interceptors can be added;
  2. A request interceptor can do some processing before a request is sent;
  3. The response interceptor can do some processing on the response before the user code gets it;
  4. AxiosInterceptors can be removed;
  5. The user can decide whether the request interceptor executes synchronously or asynchronously;
  6. The user can decide whether the interceptor executes.

Below, we will read the source code around the above six functions.

Second, interceptor source code reading

2.1 Adding interceptors

Document pointed out that we can use axios. Interceptors. Request. Use and axios. Interceptors. Response. Use to add interceptors, then step by step, we look at is how to implement this function.

axios.interceptors

The axios instance attributes are extended on axios, so we find the source of axios.interceptors in axios.js:

Portal:./lib/core/ axios.js

/**
 * Create a new instance of Axios
 *
 * @param {Object} instanceConfig The default config for the instance
 */
function Axios(instanceConfig) {
  this.defaults = instanceConfig;
  this.interceptors = {
    request: new InterceptorManager(),
    response: new InterceptorManager()
  };
}
Copy the code

So inside axios.interceptors are two interceptor managed objects

InterceptorManager

We go straight to the interceptor manager source code:

Portal:. / lib/core/InterceptorManager. Js

function InterceptorManager() { this.handlers = []; } /** * Add a new interceptor to the stack * * @param {Function} fulfilled The function to handle `then` for a `Promise`  * @param {Function} rejected The function to handle `reject` for a `Promise` * * @return {Number} An ID used to remove interceptor later */ InterceptorManager.prototype.use = function use(fulfilled, rejected, options) { this.handlers.push({ fulfilled: fulfilled, rejected: rejected, synchronous: options ? options.synchronous : false, runWhen: options ? options.runWhen : null }); return this.handlers.length - 1; };Copy the code

The InterceptorManager has only one instance attribute, handlers, which refers to an empty array; InterceptorManager. Prototype. Use accept three parameters, are fulfilled the callback, the rejected the callback and options, and then wrap the three parameters as the object of a push to the handlers, And returns the index in handlers. This is the process of adding interceptors.

2.2 How interceptors work

From the previous article: Axios source code (I) : Core functionality source code, we know that axios.prototype. request describes how the interceptor works:

Portal:./lib/core/ axios.js

// filter out skipped interceptors var requestInterceptorChain = []; var synchronousRequestInterceptors = true; Enclosing interceptors. Request. ForEach (function unshiftRequestInterceptors (interceptor) {/ / for each request interceptor execute the following code if (typeof Interceptor.runwhen === 'function' && interceptor.runwhen (config) === false) {// If the result of runWhen is false, Then do not execute the interceptor return; } // Interceptors are asynchronous by default, and only all interceptors are synchronous, Will synchronous execution synchronousRequestInterceptors = synchronousRequestInterceptors && interceptor. The synchronous; requestInterceptorChain.unshift(interceptor.fulfilled, interceptor.rejected); }); var responseInterceptorChain = []; this.interceptors.response.forEach(function pushResponseInterceptors(interceptor) { responseInterceptorChain.push(interceptor.fulfilled, interceptor.rejected); }); // If the interceptor is asynchronous, var promise is executed; if (! SynchronousRequestInterceptors) {/ / initializes a var execution chain chain = [dispatchRequest, undefined]; / / put request interceptor chain first Array. The prototype. The unshift. Apply (chain, requestInterceptorChain); Chain = chain.concat(responseInterceptorChain); promise = Promise.resolve(config); while (chain.length) { promise = promise.then(chain.shift(), chain.shift()); } return promise; } // If the interceptor is synchronous, execute var newConfig = config; While (requestInterceptorChain. Length) {/ / cycle after all request interceptor var onFulfilled = requestInterceptorChain shift (); var onRejected = requestInterceptorChain.shift(); try { newConfig = onFulfilled(newConfig); } catch (error) { onRejected(error); break; } } try { promise = dispatchRequest(newConfig); } catch (error) { return Promise.reject(error); } / / all the response appended to the interceptor dispatchRequest returns while behind the promise (responseInterceptorChain. Length) {promise = promise.then(responseInterceptorChain.shift(), responseInterceptorChain.shift()); } return promise;Copy the code

summary

Here’s a summary of the interceptor execution steps:

  1. Declare a request interceptor array and a response interceptor array before making a request.
  2. traverseaxiosRequest interceptor on the instance, executesrunWhen(if present), will result intrueInterceptor ofcallbackPress into the request interceptor array and determine whether the interceptor is synchronous or asynchronous;
  3. Will all response interceptorscallbackPressed into the response interceptor array;
  4. If all, and note that all request interceptors are synchronous (an argument will be made below), then loop through all request interceptors and return fromdispatchRequestThe start of thepromise; Otherwise, all request interceptors are executed asynchronously, returning the first interceptor executionpromise;

2.3 Removing interceptors

Sample code through axios. Interceptors. Request. Eject can remove interceptors, we find the corresponding code:

Portal:. / lib/core/InterceptorManager. Js

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

The implementation here is simple by setting the corresponding interceptor in the Handlers array to NULL, but where does this index come from? In fact, the use method returns the index of the interceptor in the Handlers array.

2.4 Synchronous and conditional execution of interceptors

Synchronous execution

InterceptorManager. Prototype. The use of receiving the third parameter as an object, on the object can be configured interceptor is synchronous or asynchronous execution, but each interceptor has this configuration items, if different configuration items, what consequence can you produce? For example, some interceptors are configured to execute synchronously and others are configured to execute asynchronously. How does the interceptor end up executing?

From the source code, it is not hard to see that interceptors execute synchronously only if all interceptors execute synchronously, otherwise they execute asynchronously. Here we demonstrate experimentally:

Axios. Interceptors. Request. Use (config = > {the console. The log (' request interceptor 1 '); return config; }, error => {}, { synchronous: true }); Axios. Interceptors. Request. Use (config = > {the console. The log (' request interceptor 2 '); return config; }, error => {}, { synchronous: false }); axios.get('/api').then(res => { console.log(res); }) console.log(' sync code execution ');Copy the code

Execution Result:

Axios. Interceptors. Request. Use (config = > {the console. The log (' request interceptor 1 '); return config; }, error => {}, { synchronous: true }); Axios. Interceptors. Request. Use (config = > {the console. The log (' request interceptor 2 '); return config; }, error => {}, { synchronous: true }); Console. log(' sync code execution ');Copy the code

Execution Result:

From the above experiments, it can be concluded that interceptors are executed synchronously only if all interceptors are executed synchronously, otherwise they are executed asynchronously.

Conditional execution

When the interceptor requires conditional execution, it simply needs to configure runWhen for use

axios.interceptors.request.use(config => {return config}, err => {}, {
    runWhen: config => {
        return config.headers.token ? true : false
    }
})
Copy the code