This post first appeared on my personal blog

This article participated in the weekly study of 200 lines of source code reading activities

Offer to come, dig friends take it! I am participating in the 2022 Spring Recruit Punch card activity. Click here for details.

We’ve all used interceptors at one point or another with Axios, mostly for different situations. For example, token is set before request, data is parsed after response and so on.

Axios interceptor code in the lib/core/InterceptorManager js, the realization of the interceptor isn’t so complicated. The main function of the ES5 interceptor is InterceptorManager.

As you can see from the figure, the implementation of interceptors is simple: a handlers to store interceptor methods, a Use to add interceptors to the Handlers, an eject to remove interceptors from the Handlers, and a forEach to iterate over interceptors.

InterceptorManager

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

I don’t need to say much about the main function, but I need to say a little bit about it.

use

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

I’m pushing interceptors directly into handlers. The original structure of the promise chain is requestInterceptorChain and responseInterceptorChain. Maybe 0 and the even index is depressing, and the odd index is rejected.

Here, each element contains the normal handler (DEPRESSING), the exception handler (Rejected), the synchronous running attribute (SYNCHRONOUS), and the runtime judge (runWhen).

This block is stored this way because:

  1. If you go straight torequestInterceptorChainresponseInterceptorChainThe array will be four times as long as the current one.
  2. Removing and traversing the interceptor would be a bit more cumbersome because, in addition to calculating offsets, it would take three more iterations than it does now.

RequestInterceptorChain and responseInterceptorChain are designed to fit the chain concept.

eject

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

Removing this code is also relatively simple. Handlers [id] = null this. Handlers [id] = null this. Handlers [id] = null this.

If you use splice every time you remove an interceptor, not to mention the high time complexity, if you do that, you remove the tail-most interceptor, which is fine, if you remove the header or the middle interceptor, This will cause all interceptor ids to change since the interceptor was removed (see above), and removing the interceptor will remove the error.

Using {} directly to remove interceptors also increases the complexity of subsequent code. Because you either need to determine if each element exists and then decide whether to pass it to forEach’s callback. Either pass it directly to the callback function, and the callback function determines. (This is even more unreasonable! Hello! Why give a callback function something that the interceptor can handle internally? Are the interceptors going to be outsourced? Hello! And that doesn’t make any sense. Hey! Why does it still exist and pass it to the callback function so that the callback function can determine whether it exists or not?

In that case, undefined or NULL is a better choice.

The reason for not using undefined is also largely a matter of semantics. After all, null represents an empty object, and the handlers array stores object types, so undefined is the same, but there is a semantic difference.

For the difference between undefined and null, refer to chapter 2 of Axios source code analysis. The conclusion is that they are not very different in their use, but the semantics and types are different.

forEach

/**
 * Iterate over all the registered interceptors
 *
 * This method is particularly useful for skipping over any
 * interceptors that may have become `null` calling `eject`.
 *
 * @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

This is the traversal function implemented internally by the interceptor. Plus this, we’ve touched on two axios officially written forEach functions. The previous one is utils.foreach.

The main purpose of this is to throw interceptor traversal to the callback function, which may seem a little confusing. Let’s break it down:

InterceptorManager.prototype.forEach = function forEach(fn) {
  utils.forEach(this.handlers, function forEachHandler(h) {... }); };Copy the code

ForEach is the same as the native array.foreach (), except that the object can be iterated over. So function forEachHandler(h) {} receives h as an interceptor element.

In a nutshell, Handlers, gestures, Handlers. ForEach (function forEachHandler(h) {}).

if(h ! = =null) {
  fn(h);
}
Copy the code

Then we look at the processing logic, which is to determine whether the interceptor is valid and pass the unremoved interceptor into the callback function fn().