Axios

Promise based HTTP client for the browser and Node.js – Promise based HTTP library that can be used in browsers and NodeJs

In the previous article, I explained how AXIos calls, interceptors, and so on. This time, it mainly analyzes dispatchRequest initiating request, canceling request, data conversion and XSRF defense.

dispatchRequest

In the interceptor’s call chain, the first call function placed is dispatchRequest, which is defined in core/dispatchRequest.js. Here is the source code:

var transformData = require('./transformData');

// Cancel the request exception handling
function throwIfCancellationRequested(config) {
  if(config.cancelToken) { config.cancelToken.throwIfRequested(); }}module.exports = function dispatchRequest(config) {
  throwIfCancellationRequested(config);

  // Support baseURL config Supports baseURL concatenation
  if(config.baseURL && ! isAbsoluteURL(config.url)) { config.url = combineURLs(config.baseURL, config.url); }// Ensure headers exist
  config.headers = config.headers || {};

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

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

  // Adapter means adapter. In axios there are two types of adapter: 1, XHR, and 2, HTTP (HTTPS) for nodejs.
  // The adapter of the browser environment is XHR, the adapter logic will be explained later
  var adapter = config.adapter || defaults.adapter;

  // Make the request promise
  return adapter(config).then(function onAdapterResolution(response) {
    // Request a successful callback
    throwIfCancellationRequested(config);

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

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

As you can see, when the request is initiated, the Config operation is performed and the transformData function is executed several times to process the request data and the response data. This is *** data conversion ***. Finally, an Adapter is returned.

emmmm… So let’s insert adapter and then we’ll talk about transformData later.

adapter

The adapter is mounted on the config. Normally we don’t specify this configuration. Use the default, which is defined in defaults.js by default:

function getDefaultAdapter() {
  var adapter;
  // Only node.js has a process variable that is of [[Class]] process nodeJS
  if (typeofprocess ! = ='undefined'
    && Object.prototype.toString.call(process) === '[object process]'
  ) {
    // For node use HTTP adapter
    adapter = require('./adapters/http');
  } else if (typeofXMLHttpRequest ! = ='undefined') { // Check the browser environment
    // For browsers use XHR adapter
    adapter = require('./adapters/xhr');
  }
  return adapter;
}

var defaults = {
  adapter: getDefaultAdapter(),
  /* something ... * /
}
module.exports = defaults;
Copy the code

The getDefaultAdapter method internally checks the current axiOS execution environment and introduces different adapters according to the environment. This time, we will only examine the implementation of the browser environment, so that /adapters/xhr.js is our focus.

module.exports = function xhrAdapter(config) {
  return new Promise(function dispatchXhrRequest(resolve, reject) {
    /* something ... Gets the argument */

    // Familiar XMLHttpRequest
    var request = new XMLHttpRequest();

    /* something ... Auth * /

    request.open(config.method.toUpperCase(), buildURL(config.url, config.params, config.paramsSerializer), true);

    request.onreadystatechange = function handleLoad() {
      if(! request || request.readyState ! = =4) {
        return;
      }
      if (request.status === 0 && !(request.responseURL && request.responseURL.indexOf('file:') = = =0)) {
        return;
      }

      /* something ... * /

      // Change the encapsulated Promise state
      settle(resolve, reject, response);
    };

    /* The middle part is the listener for some events, such as: cancel, timeout, error, request progress, XSRF, cancel request, etc. */
    request.send(requestData);
  });
};
Copy the code

Voila! You should already see the XMLHttpRequest familiar to you as a front-end developer.

transformData

Back to the transformData data conversion, axios implements JSON conversion functions here. Let’s look at the definition of transformData:

// transformData.js
module.exports = function transformData(data, headers, fns) {
  // Call the incoming FNS loop to handle the incoming data, headers
  utils.forEach(fns, function transform(fn) {
    data = fn(data, headers);
  });
  / / return data
  return data;
};
Copy the code

This loop calls the incoming config.transformResponse or config.transformRequest to process data and headers, and returns the processed data. After the request is processed is the result of the request data.

So let’s take the veil off config.transformResponse or config.transformRequest:

// defaults.js
var defaults = {
  transformRequest: [function transformRequest(data, headers) {
    // Format Accept and content-type of headers
    normalizeHeaderName(headers, 'Accept');
    normalizeHeaderName(headers, 'Content-Type');
    // If it is FormData ArrayBuffer Buffer Stream File Blob is not processed and returned directly
    if (utils.isFormData(data) ||
      utils.isArrayBuffer(data) ||
      utils.isBuffer(data) ||
      utils.isStream(data) ||
      utils.isFile(data) ||
      utils.isBlob(data)
    ) {
      return data;
    }
    // If the ArrayBuffer view model is used, the contents of the ArrayBuffer entity are fetched
    if (utils.isArrayBufferView(data)) {
      return data.buffer;
    }
    // If an object is converted to a string
    if (utils.isObject(data)) {
      setContentTypeIfUnset(headers, 'application/json; charset=utf-8');
      return JSON.stringify(data);
    }
    returndata; }].transformResponse: [function transformResponse(data) {
    // Automatic conversion to JSON is here ~~~ check if it is a string, try to convert JSON
    if (typeof data === 'string') {
      try {
        data = JSON.parse(data);
      } catch (e) { /* Ignore Ignore processing, otherwise an error will be reported, the result is not */}}returndata; }]./* something... * /
}
Copy the code

As you can see, data conversion is a default behavior of AXIos. As we learned earlier, when users use AXIos, the custom configuration takes precedence over the default configuration. Therefore, if you are not sure whether to abandon the default data conversion behavior, Add a custom transformation configuration to the default behavior when overwriting:

Such as:

axios({
  transformRequest: [
        function(data) {
            /* do something */
            return data;
        },
        ...(axios.defaults.transformRequest)
  ],
  transformResponse: [
      ...(axios.defaults.transformResponse),
      function(data) {
        /* do something */
        returndata; }].url: 'xxxx'.data: {}
}).then((res) = > {
  console.log(res.data)
})
Copy the code

Cancel the request

Axios makes clever use of the Promise feature when implementing the cancellation request. Let’s find out

Let’s take a look at how the request is unsent using AXIos:

1 / / way
const CancelToken = axios.CancelToken;
const source = CancelToken.source();

axios.get('xxxx', {
  cancelToken: source.token
})
// Cancel the request (request reason is optional)
source.cancel('Actively cancel request');

2 / / way
const CancelToken = axios.CancelToken;
let cancel;

axios.get('xxxx', {
  cancelToken: new CancelToken(function executor(c) { cancel = c; })}); cancel('Actively cancel request');
Copy the code

Canceltoken.js canceltoken.js canceltoken.js canceltoken.js canceltoken.js canceltoken.js canceltoken.js canceltoken.js canceltoken.js

function CancelToken(executor) {
  if (typeofexecutor ! = ='function') {
    throw new TypeError('executor must be a function.');
  }
  // Define a pending state promise on CancelToken, assigning the resolve callback to the external variable resolvePromise
  var resolvePromise;
  this.promise = new Promise(function promiseExecutor(resolve) {
    resolvePromise = resolve;
  });

  var token = this;
  // Execute the incoming Executor function immediately, passing the actual Cancel method as an argument.
  ResolvePromise (resolvePromise, resolvePromise, resolvePromise, resolvePromise)
  // The canceltoken.promise. then method defined in XHR is executed, and the request is cancelled internally
  executor(function cancel(message) {
    // Determine if the request has already been canceled to avoid multiple executions
    if (token.reason) {
      return;
    }
    token.reason = new Cancel(message);
    resolvePromise(token.reason);
  });
}

CancelToken.source = function source() {
  The source method returns an instance of CancelToken, the same operation as using new CancelToken directly
  var cancel;
  var token = new CancelToken(function executor(c) {
    cancel = c;
  });
  // Returns the created CancelToken instance and the cancellation method
  return {
    token: token,
    cancel: cancel
  };
};
Copy the code

The action to cancel the request is actually done in xhr.js with the response

if (config.cancelToken) {
    config.cancelToken.promise.then(function onCanceled(cancel) {
        if(! request) {return;
        }
        // Cancel the request
        request.abort();
        reject(cancel);
    });
}
Copy the code

The trick is that the executor function in CancelToken controls the state of a promise by passing and executing the resolve function.

The end of the

Most of the axios process implementation has been briefly analyzed up to this point. If there is any unreasonable or wrong place, you are welcome to point out.

The original address