This time I finally understood how Axios interrupted the request

Axios document case

Let's take a look at the examples given in the Axios documentation https://github.com/axios/axio...

  1. Cancel through the CancelToken.source factory function
const CancelToken = axios.CancelToken;
const source = CancelToken.source();

axios.get('/user/12345', {
  cancelToken: source.token
}).catch(function (thrown) {
  if (axios.isCancel(thrown)) {
    console.log('Request canceled', thrown.message);
  } else {
    // handle error
  }
});

axios.post('/user/12345', {
  name: 'new name'
}, {
  cancelToken: source.token
})

// cancel the request (the message parameter is optional)
source.cancel('Operation canceled by the user.');
  1. Cancel by CancelToken constructor
const CancelToken = axios.CancelToken;
let cancel;

axios.get('/user/12345', {
  cancelToken: new CancelToken(function executor(c) {
    // An executor function receives a cancel function as a parameter
    cancel = c;
  })
});

// cancel the request
cancel();
  1. Interrupt the request through AbortController, which is the api of fetch. This article will not introduce it in detail. For specific use, please refer to https://developer.mozilla.org...
const controller = new AbortController();

axios.get('/foo/bar', {
   signal: controller.signal
}).then(function(response) {
   //...
});
// cancel the request
controller.abort()

Source code analysis

First, you need to download the Axios source code from GitHub. If you don't want to download it, you can also open it https://github1s.com/axios/ax... View.

Factory function CancelToken.source

From the previous two examples, we can see that the cancellation request is closely related to the CancelToken class. The CancelToken.source() factory function just helps us instantiate an instance of CancelToken in an invisible interior.

Let's first look at the implementation of the factory function.

// File path: Axios/lib/cancel/CancelToken.js

// ...

/**
 * Returns an object that contains a new `CancelToken` and a function that, when called,
 * cancels the `CancelToken`.
 */
CancelToken.source = function source() {
  var cancel;
  var token = new CancelToken(function executor(c) {
    cancel = c;
  });
  return {
    token: token,
    cancel: cancel
  };
};

module.exports = CancelToken;

You can see that the factory function CancelToken.source helps us instantiate an instance of CancelToken, and then returns the instance we need to use (token) and the function to cancel the request (cancel).

Next, let's go deep into the CancelToken to see why the request is interrupted after the cancel function is executed.

CancelToken class

// File path: Axios/lib/cancel/CancelToken.js

// ...

/**
 * A `CancelToken` is an object that can be used to request cancellation of an operation.
 *
 * @class
 * @param {Function} executor The executor function.
 */
function CancelToken(executor) {
  if (typeof executor !== 'function') {
    throw new TypeError('executor must be a function.');
  }

  var resolvePromise;

  this.promise = new Promise(function promiseExecutor(resolve) {
    resolvePromise = resolve;
  });

  var token = this;

  // eslint-disable-next-line func-names
  this.promise.then(function(cancel) {
    if (!token._listeners) return;

    var i;
    var l = token._listeners.length;

    for (i = 0; i < l; i++) {
      token._listeners[i](cancel);
    }
    token._listeners = null;
  });

  // eslint-disable-next-line func-names
  this.promise.then = function(onfulfilled) { // ...
  };

  executor(function cancel(message) {
    if (token.reason) {
      // Cancellation has already been requested
      return;
    }

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

// ...

It can be seen from the examples in the document that CancelToken is passed in when the request is initiated, that is, an instance of CancelToken.

During instantiation, the executor function we passed in will be called to pass the cancel function to us.

In addition, there is a promise attribute on this instance. When we call the cancel function, promise will change from pending to fully. This triggers promise.then and executes all token_ listeners.

token._ Where do listeners come from?

The answer is still in the current file

// File path: Axios/lib/cancel/CancelToken.js

// ...

/**
 * Subscribe to the cancel signal
 */

CancelToken.prototype.subscribe = function subscribe(listener) {
  // If the reason value is not undefined, it indicates that the request has been cancelled. You can call listener directly
  if (this.reason) {
    listener(this.reason);
    return;
  }

  if (this._listeners) {
    this._listeners.push(listener);
  } else {
    this._listeners = [listener];
  }
};

// ...

The subscribe method is added to the prototype object of CancelToken to subscribe to the event of cancellation request. If the request has been cancelled, the listener will be called immediately, otherwise the listener will be saved in the_ In the listeners array.

When we call cancel, that is, cancel the request_ The listener saved in listeners is called (see above).

At this time, the interrupt request operation is not seen. The specific logic is in the listener. The reason for this writing is that it can be decoupled to improve the reusability of the code.

In addition, there is an unsubscribe, and the unsubscribe will not be expanded.

This is a typical subscription publishing model.

Cancel request

The quickest way is to search config.cancelToken.subscribe, so that you can quickly locate the specific implementation of the cancellation request.

Just search the Lib folder. You can see two places: lib/adapters/http.js and lib/adapters/xhr.js.

Because Axios is an http client that supports node.js and browser. The adapter pattern is applied here to be compatible with the two platforms. This paper studies the cancellation request, so we don't go into this part. Let's just look at one of them.

// Axios/lib/adapters/xhr.js

// ...
    if (config.cancelToken || config.signal) {
      // Handle cancellation
      // eslint-disable-next-line func-names
      onCanceled = function(cancel) {
        if (!request) {
          return;
        }
        reject(!cancel || (cancel && cancel.type) ? new Cancel('canceled') : cancel);
        request.abort();
        request = null;
      };

      config.cancelToken && config.cancelToken.subscribe(onCanceled);
      if (config.signal) {
        config.signal.aborted ? onCanceled() : config.signal.addEventListener('abort', onCanceled);
      }
    }
    
// ...

Starting from line 187, we can see that config. cancelToken. Subscribe (oncancelled) registers a callback to cancel the request. request.abort(); The request here is an instance of XMLHttpRequest.

In addition, there is a function done, that is, the oncancelled registered above will be unregistered after the request succeeds or fails.

At this point, the logic of the whole cancellation request runs through. I simply drew a picture (for a few hours), hoping to facilitate everyone's understanding.

Combined with Vue, you can leave the page and cancel the unfinished request

The idea is to use an object to manage all canceltoken instances. Before initiating the request, save the newly created canceltoken to the object, and clear the corresponding instance after the request ends (including success and failure).

Combined with the routing guard of Vue router, all outstanding requests can be cancelled when leaving the page.

Some global interfaces need special processing, such as requesting user information. These global interfaces can no longer interrupt requests when leaving the page.

The specific code is not shown here. I wrote a demo, which can be viewed by small partners in need.

https://github.com/AD-feiben/...

Finally, to reiterate, learning the source code is to learn the excellent design in the source code. We need to think about how to apply this design to our project, which is the most important point.

I hope the content of the article can provide you with a trace of help. If it is wrong, please correct it.

Tags: Javascript axios source code http

Posted on Fri, 12 Nov 2021 18:24:12 -0500 by Hi I Am Timbo