origin

The reason for reading this is because there is an old piece of Nuxt code that writes a judgment about response.status in case of an error:

const code = parseInt(error.response && error.response.status);
if ([502.504.400].includes(code) || ! code) ...Copy the code

The interceptors mounted via nuxt’s onError (one on each request and response interceptors) hit the next! The code condition causes an unexpected situation to occur.

As a serious HTTP request, the status exists, so in what cases does the status not exist under Axios?

(Not because AXIos is relatively easy to read and I can read it, no!)

The sign

Axios is an asynchronous communication library that supports promises based on XMLHttpRequest encapsulation.

Before reading the source code, it is necessary to identify the questions you want to explore:

  1. How is the Axios interceptor implemented?
  2. When can a status be lost?

xhr

The XHR encapsulation can be seen in lib/ Adapters /xhr.js and another HTTP for node.

The encapsulation of XHR is the preprocessing of some events, wrapped in a Promise. Here we know that a Promise does not make a piece of code asynchronous, but rather resolves the problem of callback hell.

module.exports = function xhrAdapter(config) {
  return new Promise(function dispatchXhrRequest(resolve, reject) {
    var request = new XMLHttpRequest();

    // Listen for ready state
    request.onreadystatechange = function handleLoad() {
      if(! request || request.readyState ! = =4) {
        return;
      }

      // The request errored out and we didn't get a response, this will be
      // handled by onerror instead
      // With one exception: request that using file: protocol, most browsers
      // will return status as 0 even though it's a successful request
      if (request.status === 0 && !(request.responseURL && request.responseURL.indexOf('file:') = = =0)) {
        return;
      }

      // Prepare the response
      var responseHeaders = 'getAllResponseHeaders' in request ? parseHeaders(request.getAllResponseHeaders()) : null;
      varresponseData = ! config.responseType || config.responseType ==='text' ? request.responseText : request.response;
      var response = {
        data: responseData,
        status: request.status,
        statusText: request.statusText,
        headers: responseHeaders,
        config: config,
        request: request
      };

      settle(resolve, reject, response);

      // Clean up request
      request = null;
    };
};
Copy the code

Onreadystatechange is the event when the state of XHR changes. ReadyState corresponds to 0,1,2,3,4. 4: The request has been completed.

module.exports = function settle(resolve, reject, response) {
  var validateStatus = response.config.validateStatus;
  if(! response.status || ! validateStatus || validateStatus(response.status)) { resolve(response); }else {
    reject(createError(
      'Request failed with status code ' + response.status,
      response.config,
      null, response.request, response )); }};Copy the code

Settle just does a layer of encapsulation.

The rest is error handling, upload handling, etc.

From the entrance

Var axios = createInstance(defaults); var axios = createInstance(defaults); And that’s what we derive.

function createInstance(defaultConfig) {
  var context = new Axios(defaultConfig);
  var instance = bind(Axios.prototype.request, context);

  // Copy axios.prototype to instance
  utils.extend(instance, Axios.prototype, context);

  // Copy context to instance
  utils.extend(instance, context);

  return instance;
}
Copy the code

Basically create an Axios, then bind to expose the Axios.request, and link the Axios prototype to the request. This is done so that it can be directly Axios ({methods: ‘get’}) or axios.get.

Axios

In lib/core/ axios.js, constructor defines two properties,

function Axios(instanceConfig) {
  this.defaults = instanceConfig;
  this.interceptors = {
    request: new InterceptorManager(),
    response: new InterceptorManager()
  };
}

Copy the code

So defautls are the defaults that we passed in when we created the instance above, from defaults.js, which we don’t need now and we’ll see later.

Skip request and getUri for a moment and find the definitions of methods such as GET:

utils.forEach(['delete'.'get'.'head'.'options'].function forEachMethodNoData(method) {
  /*eslint func-names:0*/
  Axios.prototype[method] = function(url, config) {
    return this.request(mergeConfig(config || {}, {
      method: method,
      url: url,
      data: (config || {}).data
    }));
  };
});

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

So get, post, all of those things are extended by the request method, so if you go back to the request method, forEach, mergeConfig, you can guess what the name is.

The core request

Axios.prototype.request = function request(config) {
  /*eslint no-param-reassign:0*/
  // Allow for axios('example/url'[, config]) a la fetch API
  if (typeof config === 'string') {
    config = arguments[1) | | {}; config.url =arguments[0];
  } else {
    config = config || {};
  }

  config = mergeConfig(this.defaults, config);

  // Set config.method
  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
  var chain = [dispatchRequest, undefined];
  var promise = Promise.resolve(config);

  this.interceptors.request.forEach(function unshiftRequestInterceptors(interceptor) {
    chain.unshift(interceptor.fulfilled, interceptor.rejected);
  });

  this.interceptors.response.forEach(function pushResponseInterceptors(interceptor) {
    chain.push(interceptor.fulfilled, interceptor.rejected);
  });

  while (chain.length) {
    promise = promise.then(chain.shift(), chain.shift());
  }

  return promise;
};
Copy the code

The request code isn’t long. First, it’s a wrapper around the string, because the comment says how Axios allows axios(‘ URL ‘).

And then you have a Config merge, and the merge defaults are expanded after that.

This is followed by a lowercase conversion to the HTTP request method, which defaults to GET.

The last issue to address is ———— how the Axios interceptor is implemented:

The chain initialization has two elements, dispatchRequest and undefined, dispatchRequest as you can guess from its name is related to the content of the request.

Promise. Resolve (config) returns a resolved Promise. If you continue using then, you can insert a resolved Promise. Another application for microtasks is nextTick in Vue.

During initialization, the two interceptors, request/ Response, do an iteration respectively. Request is placed on the left and added from the beginning of the queue, while Response is placed on the right and added from the end of the queue.

It looks something like this:

chain = [
         request.fulfilled, request.rejected,  // Add a pre-request interceptor
         dispatchRequest, undefined.// Initiate a request
         response.fulfilled, response,rejected // Respond to interceptors
        ]
Copy the code

The chain was then added to the microtask using the Promise chain-call feature.

The then method of a Promise can pass two arguments. The first is a callback that everyone knows about, and the second is an error handler that I didn’t know about. Like a catch, the second is executed if the execution fails.

a = Promise.resolve('1')
b = [ () = >{ console.log(123)},() = > { console.log(8889)},() = > { return new Promise((resolve, reject) = > { setTimeout(() = > { console.log(999); reject(888)},3000)})},(res) = > { console.log(res) }]
b.map((item) = > { a = a.then(item, item) }

Copy the code

Of course, the default is undefined when adding an interceptor using the use method:

InterceptorManager.prototype.use = function use(fulfilled, rejected) {
  this.handlers.push({
    fulfilled: fulfilled,
    rejected: rejected // If not passed, undefined
  });
  return this.handlers.length - 1;
};
Copy the code

So if something goes wrong here it’s going to report an error and it’s not going to continue.

Here’s dispatchRequest:

module.exports = function dispatchRequest(config) {
  throwIfCancellationRequested(config);

  // Ensure headers exist
  config.headers = config.headers || {};

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

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

  utils.forEach(
    ['delete'.'get'.'head'.'post'.'put'.'patch'.'common'].function cleanHeaderConfig(method) {
      deleteconfig.headers[method]; });var adapter = config.adapter || defaults.adapter;

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

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

    return response;
  }, function onAdapterRejection(reason) {
    if(! isCancel(reason)) { throwIfCancellationRequested(config);// Transform 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

The dispatchRequest is going to return the Config adapter, and the config adapter is going to be passed in by the request, and we’re not going to pass that parameter when we write, so we’re going to merge it in by default.

This defaults is what we pass in Axios when we create it, and you can find that in Axios.js, the contents of imported defaults.js.

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

The COMPATIBILITY of XMLHttpRequest is still good, so it’s all platform and all browser (as long as it’s not overridden).

XHR, which we looked at initially, returns a Promise and is set to complete by settling after the request is returned.

Here are two points to note:

  1. The response that is returned in the. Then of dispatchRequest is the response that is accepted by the response interceptor (or returned directly) rather than the response that is resolved/rejected after settle.
  2. .thenreturnThis Promise would not otherwise block, and the result of not blocking is that subsequent interceptors will execute before the request completes.

The entire implementation process of the interceptor is clear:

  1. throughrequests.use((conf) => {})The interceptors added byunshiftAdd before the request.
  2. throughresponse.use((conf) => {})The interceptors added bypushAdd after the request.
  3. This sequence of operations is done through the chained invocation capability provided by Promise.

Under what circumstances does status not exist

From the above analysis and the implementation of the interceptor, we know that status is encapsulated in response by Axios in xhr.js:

      var response = {
        data: responseData,
        status: request.status,
        statusText: request.statusText,
        headers: responseHeaders,
        config: config,
        request: request
      };

      settle(resolve, reject, response);
Copy the code

Once you get to that point, either resolve or Reject by default passes the response all the way.

This response does not exist for events such as onError and onTimeout registered directly on XHR.

If your XHR request is returned by the server and the status is reached, there will be no status if the request is not returned for any reason.

In this case, the result conforms to the expectation that all serious HTTP requests have a status.