A few days ago, I analyzed Axios source code design 🔗, where the interceptor left a gateway for extending Axios. In our work, we often extend Axios, such as: cancel repeated requests, permission validation, retry failure, etc.

So how do you design and implement a good interceptor to extend Axios?

Learn about axios-Retry’s 100W + download triparty library this week to learn about its feature design, the sourcing strategy of the tool library project, and use this to improve our coding and design capabilities!

Package. json (package.json

To see the source code for a module, first look at the readme. md and package.json files.

As mentioned above, we should also pay attention to the following fields when developing tool libraries in the future:

  • files: Will publish when the package is sentes,libTwo folders, as wellindex.jsindex.d.tsFile.
  • typingsThe: TypeScript type definition file is used for intelligent type hints in the TypeScript encoding environment. This field can also be writtentypes.
  • main: main entry file, indicating that when the current library is introduced into the project, the default file to point to isindex.js
  • Module: non-official field, the packaging tool convention, if present, will handle the file path specified to import the ESM version of our library when, for example, Rollup and Webpack packaging.
  • Exports: provides a way to declare how to import modules for different environments and JavaScript styles while limiting access to their internal parts. Bare Module Specifier Resolution in Node.js

With the dependency field and the scripts field:

As can be seen, the current project directly uses Babel as a package compilation tool, and executes the following tasks successively by executing NPM Run release and combining the pre and POST execution life cycle of NPM scripts:

For more information about the package.json field, see package. json-npm

Second, source code analysis

According to the interpretation of the “send package” command in package.json file, it can be known that the index.mjs in the./es/ folder is the function implementation file.

2.1 Why is the.mjs file name suffix

The original node. js module system is CommonJs (using require and module.exports syntax).

Since the creation of Node.js, the ECMAScript module system (using import and export syntax) has become a standard, and Node.js has added and implemented support for the ES module system.

Node.js treats *.cjs files as CommonJS modules and *.mjs files as ECMAScript modules. It treats the.js file as the project’s default module system, which is CommonJS unless package.json declares “type”: “module”.

2.2 Usage of AXIos-Retry

Axios-retry ()

Extend the ability to automatically retry network requests by adding an “interceptor” to axios singletons.

Axios-retry takes two main arguments, the first being an AXIos instance and the second being the configuration defaultOptions for AXIos-Retry:

defaultOptions: { retries? :number; // Number of automatic retriesshouldResetTimeout? :boolean; // Whether to reset the timeout periodretryCondition? :Function; // The conditions for retries can be passed in custom judgment functionsretryDelay? :Function;  // The interval between retries
}
Copy the code

The features look perfect. No wonder it’s so popular.

2.3 Request interceptor design & implementation

State initialization is done in the request interceptor to update the number of requests:

axios.interceptors.request.use((config) = > {
  const currentState = getCurrentState(config);
  // Set the last request time
  // think 🤔 : why not put it inside getCurrentState()?
  currentState.lastRequestTime = Date.now();
  return config;
});
Copy the code
/** * Initializes and returns the retry status for the given "request" and "configuration" *@param  {AxiosRequestConfig} config
 * @return {Object}* /
function getCurrentState(config) {
  // Get the status from config
  const currentState = config[namespace) | | {};// Record the number of current requests
  currentState.retryCount = currentState.retryCount || 0;
  // Update/write the current request status in config
  config[namespace] = currentState;
  return currentState;
}
Copy the code

By injecting axios-Retry fields into AXIos Config as fields to store the request status, the current request status can be retrieved from AXIos Config at any time in axios’ request execution chain.

In addition, we see the request in the interceptor didn’t set the function of reject, maybe it can be added according to reject response function, used to after the request was abnormal, can be directly do not need to retry the request, because the error request configuration must be meaningless network request, it would be pointless to retry the request, exit directly interrupt request execution chain.

A few references to the discussion of exiting the Promise execution chain:

  • How do you stop the Promise chain
  • The chain call and abort of a Promise

2.4 Response interceptor design & implementation

In the interceptor, the reject function responds only, meaning that the current interceptor is executed only if an error (an exception is thrown) occurs during the AXIOS response phase.

axios.interceptors.response.use(null.async (error) => {
  const { config } = error;

  // If no config is read, exit, possibly due to some other exception
  // For example: cancel request initiatively, is a direct throw error
  if(! config) {return Promise.reject(error);
  }

  // Read and set defaults from defaultOptions
  const {
    retries = 3.// Automatically retry three times by default
    retryCondition = isNetworkOrIdempotentRequestError,
    retryDelay = noDelay,
    shouldResetTimeout = false
  } = getRequestOptions(config, defaultOptions);

  const currentState = getCurrentState(config);

  // Determine whether to retry
  if (await shouldRetry(retries, retryCondition, currentState, error)) {
    // If desired, currentState needs to update the number of retries
    currentState.retryCount += 1;
    const delay = retryDelay(currentState.retryCount, error);

    // Axios failed to merge the default configuration because of the loop structure
    Reference issue: / / https://github.com/mzabriskie/axios/issues/370
    fixConfig(axios, config);

    // shouldResetTimeout defaults to false
    // According to the actual request time and compare config.timeout, select the maximum value to set the timeout period
    if(! shouldResetTimeout && config.timeout && currentState.lastRequestTime) {const lastRequestDuration = Date.now() - currentState.lastRequestTime;
      // Minimum 1ms timeout (passing 0 or less to XHR means no timeout)
      // Set the minimum timeout period to 1ms (XHR requests <= 0 are not considered timeout)
      config.timeout = Math.max(config.timeout - lastRequestDuration - delay, 1);
    }

    config.transformRequest = [(data) = > data];
    
    // The usual way to write Promise delays (sleep)
    // Re-initiate the request and call axios(config)
    // Because any type of request will be normalized to axios(config)
    // A compatible conversion is made to axios.prototye.request in the application layer
    return new Promise((resolve) = > setTimeout(() = > resolve(axios(config)), delay));
  }

  return Promise.reject(error);
});
Copy the code

conclusion

This is a supplement to the axios source code analysis article. As a common extension to AXIos, axios-Retry is a good example to use as a template for extending Axios functionality later.

In addition, direct packaging through Babel in Axios-Retry and its use of the NPM Scripts lifecycle to connect test, update, package build, release, Git push are also worth learning.

As mentioned in the article, you can add the error processing before “initiating network request” in the request interceptor. If any error occurs, the retry process can be directly interrupted to avoid repeated initiation of wrong requests and save computing resources. You can try to implement it.

In the response interceptor, shouldRetry() is used to check whether or not the request needs to be retried. However, in the AXIos request chain, the response interceptor always dispachRequest() event. So this attempt can still be studied at 🧐, which is of great benefit to understanding the Promise execution chain.