Writing in the front

This article is axios learning source notes, the original video is still silicon valley axios teaching video, link is as follows: www.bilibili.com/video/BV1NJ…

Axios versus Axios

  1. Axios is a constructor, Axios is a function
  2. Syntactically, Axios is not an instance built by Axios, but functionally, Axios has the functionality (properties and methods) of axios instances.
  3. Axios is the function returned by axios.prototype. request function bind()

Illustration:

Axios differs from the instance returned by axios.create

  1. Axios has create, CancelToken, all, etc
  2. The configuration of axios and instance (axios.defaults+createConfig) may be different

Axios.js:

function createInstance(defaultConfig) {
  // Create the Axios instance object context
  var context = new Axios(defaultConfig);
  // Apply axios.prototype. request's this to the binding context,
  // The new function returns an assignment to instance
  debugger
  var instance = bind(Axios.prototype.request, context);

  // Add the methods on the prototype to instance
  utils.extend(instance, Axios.prototype, context);

  // Add attributes (defaults and Interceptors) from context (Axios prototype instance) to instance
  utils.extend(instance, context);

  return instance;
}

// Create axios for module output (createInstance called)
var axios = createInstance(defaults);

// Expose Axios class to allow class inheritance
// Expose the Axios class to implement class inheritance
axios.Axios = Axios;

// Define the create method
axios.create = function create(instanceConfig) {
  return createInstance(mergeConfig(axios.defaults, instanceConfig));
};

// Expose Cancel & CancelToken
axios.Cancel = require('./cancel/Cancel');
axios.CancelToken = require('./cancel/CancelToken');
axios.isCancel = require('./cancel/isCancel');

// Expose all/spread
axios.all = function all(promises) {
  return Promise.all(promises);
};
axios.spread = require('./helpers/spread');

// Expose isAxiosError
axios.isAxiosError = require('./helpers/isAxiosError');
Copy the code

The overall process that Axios runs

The diagram shows the three most important axiOS runtime functions: Request(config) => dispatchRequest(config) => xhrAdapter(config)

request

Code:

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 main part of the request function code is as follows, which is an important implementation of the request function logic:

  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

This code begins by defining a chain array that pushes the callback function of the request interceptor to the left of the chain and the callback function of the response interceptor to the right of the chain through the unshift and push methods of the array, with the dispatchRequest function in the middle of the chain. We then complete the transition from request interceptor to response interceptor by making a promise call by popping the chain array element from left to right. As an example, we have prepared two request interceptors, requestInt1 and requestInt2, and two response interceptors, responseInt1 and responseInt2, to illustrate how the four interceptors are placed in the chain array and finally called by the chain.

As you can see from the figure, the chain array initially defines a combination of undefined and dispatchRequest to prevent out-of-order chain calls since the promise chain calls are made in pairs (interceptor success callback + failure callback).

dispatchRequest

Process of the dispatchRequest module

Convert the request data => Call xhrAdapter() to send the request => When the request returns, convert the response data and return a Promise object

Convert request data

  // 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
  );

Copy the code

This part is mainly used to transform the defaults module’s transformRequest function, and the code is as follows:

transformRequest: [function transformRequest(data, headers) {
    normalizeHeaderName(headers, 'Accept');
    normalizeHeaderName(headers, 'Content-Type');
    if (utils.isFormData(data) ||
      utils.isArrayBuffer(data) ||
      utils.isBuffer(data) ||
      utils.isStream(data) ||
      utils.isFile(data) ||
      utils.isBlob(data)
    ) {
      return data;
    }
    if (utils.isArrayBufferView(data)) {
      return data.buffer;
    }
    if (utils.isURLSearchParams(data)) {
      setContentTypeIfUnset(headers, 'application/x-www-form-urlencoded; charset=utf-8');
      return data.toString();
    }
    if (utils.isObject(data)) {
      setContentTypeIfUnset(headers, 'application/json; charset=utf-8');
      return JSON.stringify(data);
    }
    return data;
  }]
Copy the code

Transform the response data when the request returns

  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;
  }
Copy the code

This part of the code is used in the Promise callback after the Adapter call, and the function used for the transformation is the Defaults module’s transformResponse function.

  transformResponse: [function transformResponse(data) {
    /*eslint no-param-reassign:0*/
    if (typeof data === 'string') {
      try {
        data = JSON.parse(data);
      } catch (e) { /* Ignore */}}return data;
  }]
Copy the code

This function uses a try cache structure to prevent errors if data is a non-JSON string.

xhrAdapter

The flow of xhrAdapter module

Create an XHR object (the lowest level, with AJAX calls), set it up according to config, send a specific request, accept the data, and return a promise. The main code for the xhrAdapter module is as follows (from xhr.open to the onreadyStatechange section) :

module.exports = function xhrAdapter(config) {
  return new Promise(function dispatchXhrRequest(resolve, reject) {
    var requestData = config.data;
    var requestHeaders = config.headers;

    if (utils.isFormData(requestData)) {
      delete requestHeaders['Content-Type']; // Let the browser set it
    }

    var request = new XMLHttpRequest();

    // HTTP basic authentication
    if (config.auth) {
      var username = config.auth.username || ' ';
      var password = config.auth.password ? unescape(encodeURIComponent(config.auth.password)) : ' ';
      requestHeaders.Authorization = 'Basic ' + btoa(username + ':' + password);
    }

    var fullPath = buildFullPath(config.baseURL, config.url);
    request.open(config.method.toUpperCase(), buildURL(fullPath, config.params, config.paramsSerializer), true);

    // Set the request timeout in MS
    request.timeout = config.timeout;

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

As you can see, this part returns a Promise object, and the determination of the state of the object is given to the settle function. This part of the function code looks like this:

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

This section of validateStatus is the Axios config setting that defines the legal range for the return status code. The default range is [200,299].

Cancel the request

The implementation of the AXIos cancel request involves the configuration of the cancelToken in config. Let’s first look at the usage of the cancelToken (the cancel function can pass in the argument — cancellation reason) :

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 parametercancel = c; })});// cancel the request
cancel();

Copy the code

The CancelTOken section has the following source code:

function CancelToken(executor) {
  if (typeofexecutor ! = ='function') {
    throw new TypeError('executor must be a function.');
  }
  // Prepare a Promise object Promise for the cancellation request and store its resolve function outside
  var resolvePromise;
  this.promise = new Promise(function promiseExecutor(resolve) {
    resolvePromise = resolve;
  });
  // Save the current token object
  var token = this;
  executor(function cancel(message) {
    // If there is a Reason attribute in the token, cancellation has been requested
    if (token.reason) {
      // Cancellation has already been requested
      return;
    }
    //token.reason specifies a Cancel object
    token.reason = new Cancel(message);
    resolvePromise(token.reason);
  });
}
Copy the code

Using the executor function parameters, the cancel function defined internally by CancelToken is subtly passed to the external caller. The cancel function is called when the request needs to be cancelled, making the resolvePromise state progressively and the value reason. The call to the Promisethen method of CancelToken is written in xhr.js:

    if (config.cancelToken) {
      // Handle cancellation
      config.cancelToken.promise.then(function onCanceled(cancel) {
        if(! request) {return;
        }
        // Cancel the request
        request.abort();
        // Make the request promise fail
        reject(cancel);
        // Clean up request
        request = null;
      });
    }
Copy the code

Note that cancel in this section is not the previous Cancel function, but rather a Cancel object, which is the token.reason of the canceltoken.js module. The details of calling cancel() are as follows: