The principle and source code analysis of axios

I. Use of axios

The basic use of the axios has been covered in the previous article and is reviewed here:

Send Request

import axios from 'axios';

axios(config) // Pass configuration directly
axios(url[, config]) // incomingurland configure
axios[method](url[, option]) // Directly call the request method,incomingurland configure
axios[method](url[, data[, option]]) // Directly call the request method,incomingdata、urland configure
axios.request(option) // transfer request method

const axiosInstance = axios.create(config)
// axiosInstance Also has the above axios Ability

axios.all([axiosInstance1, axiosInstance2]).then(axios.spread(response1, response2))
// transfer all and incoming spread callback 

Request Blocker

axios.interceptors.request.use(function (config) {
    // Write here the code for processing before sending the request
    return config;
}, function (error) {
    // Write here the code related to sending request errors
    return Promise.reject(error);
}); 

Response Blocker

axios.interceptors.response.use(function (response) {
    // Here is the code for post-processing of the response data.
    return response;
}, function (error) {
    // Here is the code to handle the error response
    return Promise.reject(error);
}); 

Cancel Request

// method one
const CancelToken = axios.CancelToken;
const source = CancelToken.source();

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

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

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

2. Implement a simple version of axios

Build a Axios constructor with core code request

class Axios {
    constructor() {

    }

    request(config) {
        return new Promise(resolve => {
            const {url = '', method = 'get', data = {}} = config;
            // sendajaxask
            const xhr = new XMLHttpRequest();
            xhr.open(method, url, true);
            xhr.onload = function() {
                console.log(xhr.responseText)
                resolve(xhr.responseText);
            }
            xhr.send(data);
        })
    }
} 

Export axios Instance

// final exportaxiosMethods,i.e. instancerequestmethod
function CreateAxiosFn() {
    let axios = new Axios();
    let req = axios.request.bind(axios);
    return req;
}

// Get the final global variableaxios
let axios = CreateAxiosFn(); 

The above can already implement the request in this way axios({}).

The following is to implement the request in this form axios.method().

// definitionget,post...method,hanging on toAxiosOn prototype
const methodsArr = ['get', 'delete', 'head', 'options', 'put', 'patch', 'post'];
methodsArr.forEach(met => {
    Axios.prototype[met] = function() {
        console.log('implement'+met+'method');
        // Handle a single method
        if (['get', 'delete', 'head', 'options'].includes(met)) { // 2parameters(url[, config])
            return this.request({
                method: met,
                url: arguments[0],
                ...arguments[1] || {}
            })
        } else { // 3parameters(url[,data[,config]])
            return this.request({
                method: met,
                url: arguments[0],
                data: arguments[1] || {},
                ...arguments[2] || {}
            })
        }

    }
}) 

Move the method on Axios.prototype to request

Implement tool classes first, implement mixing b methods into a, and modify this points to

const utils = {
  extend(a,b, context) {
    for(let key in b) {
      if (b.hasOwnProperty(key)) {
        if (typeof b[key] === 'function') {
          a[key] = b[key].bind(context);
        } else {
          a[key] = b[key]
        }
      }

    }
  }
} 

Modify the exported method

function CreateAxiosFn() {
  let axios = new Axios();

  let req = axios.request.bind(axios);
  // Add code
  utils.extend(req, Axios.prototype, axios)

  return req;
} 

Construct constructor for interceptor

class InterceptorsManage {
  constructor() {
    this.handlers = [];
  }

  use(fullfield, rejected) {
    this.handlers.push({
      fullfield,
      rejected
    })
  }
} 

Implement axios.interceptors.response.use and axios.interceptors.request.use

class Axios {
    constructor() {
        // Add code
        this.interceptors = {
            request: new InterceptorsManage,
            response: new InterceptorsManage
        }
    }

    request(config) {
        ...
    }
} 

When the statements axios.interceptors.response.use and axios.interceptors.request.use are executed, the interceptors object on the axios instance is acquired, and then the response or request interceptor is acquired, and then the use method of the corresponding interceptor is executed.

Move methods and attributes on Axios to request past

function CreateAxiosFn() {
  let axios = new Axios();

  let req = axios.request.bind(axios);
  // mix-in method, deal withaxiosofrequestmethod,to possessget,post...method
  utils.extend(req, Axios.prototype, axios)
  // Add code
  utils.extend(req, axios)
  return req;
} 

Now that request also has interceptors objects, the request interceptor’s handlers method is executed when the request is sent

First package the request to execute ajax into a method

request(config) {
    this.sendAjax(config)
}
sendAjax(config){
    return new Promise(resolve => {
        const {url = '', method = 'get', data = {}} = config;
        // sendajaxask
        console.log(config);
        const xhr = new XMLHttpRequest();
        xhr.open(method, url, true);
        xhr.onload = function() {
            console.log(xhr.responseText)
            resolve(xhr.responseText);
        };
        xhr.send(data);
    })
} 

Get callback in handlers

request(config) {
    // Interceptors and request assembly queues
    let chain = [this.sendAjax.bind(this), undefined] // appearing in pairs,Failure callback is not processed temporarily

    // request interception
    this.interceptors.request.handlers.forEach(interceptor => {
        chain.unshift(interceptor.fullfield, interceptor.rejected)
    })

    // response interception
    this.interceptors.response.handlers.forEach(interceptor => {
        chain.push(interceptor.fullfield, interceptor.rejected)
    })

    // execution queue,Execute one pair at a time,and givepromiseAssign the latest value
    let promise = Promise.resolve(config);
    while(chain.length > 0) {
        promise = promise.then(chain.shift(), chain.shift())
    }
    return promise;
} 

chains roughly ['fulfilled1','reject1','fulfilled2','reject2','this.sendAjax','undefined','fulfilled2','reject2','fulfilled1','reject1'] this form

This makes it possible to successfully implement a simple version axios

III. Source Code Analysis

There are many ways to implement the send request, and the entry file is .

function createInstance(defaultConfig) {
  var context = new Axios(defaultConfig);

  // instancepointedrequestmethod,and the context points tocontext,So you can directly instance(option) method call 
  // Axios.prototype.request Internal judgment of the data type of the first parameter,enable us to instance(url, option) method call
  var instance = bind(Axios.prototype.request, context);

  // BundleAxios.prototypeThe method on extends toinstanceon object,
  // and specify the context ascontext,Execute like thisAxiosmethod on the prototype chain,thiswill point tocontext
  utils.extend(instance, Axios.prototype, context);

  // Copy context to instance
  // BundlecontextOwn properties and methods on the object extend toinstancesuperior
  // Note:becauseextendfor internal useforEachmethod does something to an objectfor in When traversing,Only traverse the properties of the object itself,without traversing the properties on the prototype chain
  // so,instance There it is  defaults、interceptors Attributes。
  utils.extend(instance, context);
  return instance;
}

// Create the default instance to be exported Create a generated by default configurationaxiosExample
var axios = createInstance(defaults);

// Factory for creating new instances Expandaxios.createFactory function,inside too createInstance
axios.create = function create(instanceConfig) {
  return createInstance(mergeConfig(axios.defaults, instanceConfig));
};

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

axios.spread = function spread(callback) {
  return function wrap(arr) {
    return callback.apply(null, arr);
  };
};
module.exports = axios; 

The main core is

// final exportaxiosMethods,i.e. instancerequestmethod
function CreateAxiosFn() {
    let axios = new Axios();
    let req = axios.request.bind(axios);
    return req;
}

// Get the final global variableaxios
let axios = CreateAxiosFn(); 
```. The invocation of various request modes is implemented inside 

```javascript
// final exportaxiosMethods,i.e. instancerequestmethod
function CreateAxiosFn() {
    let axios = new Axios();
    let req = axios.request.bind(axios);
    return req;
}

// Get the final global variableaxios
let axios = CreateAxiosFn(); 
```. Look at the logic of 

```javascript
// final exportaxiosMethods,i.e. instancerequestmethod
function CreateAxiosFn() {
    let axios = new Axios();
    let req = axios.request.bind(axios);
    return req;
}

// Get the final global variableaxios
let axios = CreateAxiosFn(); 
```.


```javascript
Axios.prototype.request = function request(config) {
  // Allow for axios('example/url'[, config]) a la fetch API
  // judgment config Whether the parameter is string,If so, the first parameter is considered to be URL,The second argument is trueconfig
  if (typeof config === 'string') {
    config = arguments[1] || {};
    // Bundle url place to config in object,Convenient for later mergeConfig
    config.url = arguments[0];
  } else {
    // if config Whether the parameter is string,then the whole is regarded asconfig
    config = config || {};
  }
  // Merge default configuration and incoming configuration
  config = mergeConfig(this.defaults, config);
  // Set request method
  config.method = config.method ? config.method.toLowerCase() : 'get';
  /*
    something... This part will be discussed separately in the subsequent interceptor
  */
};

// exist Axios Mount on prototype 'delete', 'get', 'head', 'options' Request method without passing parameters,Implementation internals are also request
utils.forEach(['delete', 'get', 'head', 'options'], function forEachMethodNoData(method) {
  Axios.prototype[method] = function(url, config) {
    return this.request(utils.merge(config || {}, {
      method: method,
      url: url
    }));
  };
});

// exist Axios Mount on prototype 'post', 'put', 'patch' And the request method of passing parameters,The same is true inside the implementation request
utils.forEach(['post', 'put', 'patch'], function forEachMethodWithData(method) {
  Axios.prototype[method] = function(url, data, config) {
    return this.request(utils.merge(config || {}, {
      method: method,
      url: url,
      data: data
    }));
  };
}); 

The entry parameter of request is config. It can be said that config implements the lifetime of axios.

In axios, config is mainly distributed in these places:

  • Default configuration defaults.js
  • config.method defaults to get
  • Call the createInstance method to create an instance of axios, passed in config
  • Direct or indirect invocation of request method, incoming config
// axios.js
// Create a generated by default configurationaxiosExample
var axios = createInstance(defaults);

// Expandaxios.createFactory function,inside too createInstance
axios.create = function create(instanceConfig) {
  return createInstance(mergeConfig(axios.defaults, instanceConfig));
};

// Axios.js
// Merge default configuration and incoming configuration
config = mergeConfig(this.defaults, config);
// Set request method
config.method = config.method ? config.method.toLowerCase() : 'get'; 

From the source code, you can see the priority: Instance attribute this.default < request parameter of the default configuration object default < method:get < Axios

Let’s focus on the

const utils = {
  extend(a,b, context) {
    for(let key in b) {
      if (b.hasOwnProperty(key)) {
        if (typeof b[key] === 'function') {
          a[key] = b[key].bind(context);
        } else {
          a[key] = b[key]
        }
      }

    }
  }
} 
``` approach


```javascript
Axios.prototype.request = function request(config) {
  /*
    First mergeConfig ... wait,no more elaboration
  */
  // Hook up interceptors middleware Create interceptor chain. dispatchRequest is the top priority,Follow-up focus
  var chain = [dispatchRequest, undefined];

  // pushEach interceptor method Notice:interceptor.fulfilled or interceptor.rejected is possible forundefined
  this.interceptors.request.forEach(function unshiftRequestInterceptors(interceptor) {
    // Request interceptor reverse order Note here forEach It’s a custom interceptorforEachmethod
    chain.unshift(interceptor.fulfilled, interceptor.rejected);
  });

  this.interceptors.response.forEach(function pushResponseInterceptors(interceptor) {
    // Response interceptor order Note here forEach It’s a custom interceptorforEachmethod
    chain.push(interceptor.fulfilled, interceptor.rejected);
  });

  // initialize apromiseobject,The status isresolved,The received parameters have been processed and mergedconfigobject
  var promise = Promise.resolve(config);

  // chain of loop interceptors
  while (chain.length) {
    promise = promise.then(chain.shift(), chain.shift()); // Every time the interceptor pops out
  }
  // return promise
  return promise;
}; 

Interceptor interceptors is an instantiated property in build axios

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

InterceptorManager constructor

// Initialization of interceptor In fact, it is a set of hook functions
function InterceptorManager() {
  this.handlers = [];
}

// Call the interceptor instanceuseis to go to the hook functionpushmethod
InterceptorManager.prototype.use = function use(fulfilled, rejected) {
  this.handlers.push({
    fulfilled: fulfilled,
    rejected: rejected
  });
  return this.handlers.length - 1;
};

// Interceptors can be canceled,according tousereturned whenID,Set an interceptor method tonull
// The ____ does not work splice or slice The reason is After deletion id will change,As a result, the subsequent sequence or operations are uncontrollable.
InterceptorManager.prototype.eject = function eject(id) {
  if (this.handlers[id]) {
    this.handlers[id] = null;
  }
};

// This is where Axiosofrequestin method Mid-loop interceptor methods forEach Loop execution hook function
InterceptorManager.prototype.forEach = function forEach(fn) {
  utils.forEach(this.handlers, function forEachHandler(h) {
    if (h !== null) {
      fn(h);
    }
  });
} 

The request interceptor method is unshift into the interceptor, and the response interceptor is push into the interceptor. Eventually they splice a method called dispatchRequest to be executed in subsequent promise sequences

var utils = require('./../utils');
var transformData = require('./transformData');
var isCancel = require('../cancel/isCancel');
var defaults = require('../defaults');
var isAbsoluteURL = require('./../helpers/isAbsoluteURL');
var combineURLs = require('./../helpers/combineURLs');

// Determine whether the request has been canceled,if it has been canceled,throw canceled
function throwIfCancellationRequested(config) {
  if (config.cancelToken) {
    config.cancelToken.throwIfRequested();
  }
}

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

  // if containsbaseUrl, and notconfig.urlabsolute path,combinationbaseUrlas well asconfig.url
  if (config.baseURL && !isAbsoluteURL(config.url)) {
    // combinationbaseURLandurlForm a complete request path
    config.url = combineURLs(config.baseURL, config.url);
  }

  config.headers = config.headers || {};

  // use/lib/defaults.jsneutraltransformRequestmethod,rightconfig.headersandconfig.dataFormat
  // For example,headersneutralAccept,Content-TypeUniformly processed into uppercase letters
  // For example, if the request body is aObjectwill be formatted asJSONstring,and addapplication/json;charset=utf-8ofContent-Type
  // Wait for a series of operations
  config.data = transformData(
    config.data,
    config.headers,
    config.transformRequest
  );

  // Merge different configurationsheaders,config.headersThe configuration priority of
  config.headers = utils.merge(
    config.headers.common || {},
    config.headers[config.method] || {},
    config.headers || {}
  );

  // deleteheadersneutralmethodAttributes
  utils.forEach(
    ['delete', 'get', 'head', 'post', 'put', 'patch', 'common'],
    function cleanHeaderConfig(method) {
      delete config.headers[method];
    }
  );

  // ifconfigConfiguredadapter,useconfigmedium placementadapterAn alternative to the default request method
  var adapter = config.adapter || defaults.adapter;

  // useadapterMethod to initiate a request(adapterDepending on the browser environment orNodeThe environment will be different)
  return adapter(config).then(
    // Request a callback that returns correctly
    function onAdapterResolution(response) {
      // Determine whether and cancel the request,If the request is canceled throw to cancel
      throwIfCancellationRequested(config);

      // use/lib/defaults.jsneutraltransformResponsemethod,Format the data returned by the server
      // For example,useJSON.parseParse the response body
      response.data = transformData(
        response.data,
        response.headers,
        config.transformResponse
      );

      return response;
    },
    // Request failed callback
    function onAdapterRejection(reason) {
      if (!isCancel(reason)) {
        throwIfCancellationRequested(config);

        if (reason && reason.response) {
          reason.response.data = transformData(
            reason.response.data,
            reason.response.headers,
            config.transformResponse
          );
        }
      }
      return Promise.reject(reason);
    }
  );
}; 

See how

function CreateAxiosFn() {
  let axios = new Axios();

  let req = axios.request.bind(axios);
  // Add code
  utils.extend(req, Axios.prototype, axios)

  return req;
} 
``` implements the cancellation request. The implementation file is in 

```javascript
function CreateAxiosFn() {
  let axios = new Axios();

  let req = axios.request.bind(axios);
  // Add code
  utils.extend(req, Axios.prototype, axios)

  return req;
} 
```.


```javascript
function CancelToken(executor) {
  if (typeof executor !== 'function') {
    throw new TypeError('executor must be a function.');
  }
  // exist CancelToken Define a pending status promise ,Will resolve Callback assigned to external variable resolvePromise
  var resolvePromise;
  this.promise = new Promise(function promiseExecutor(resolve) {
    resolvePromise = resolve;
  });

  var token = this;
  // Execute immediately incoming executorfunction,will be true cancel Methods are passed through parameters。
  // Execute once called resolvePromise That is, the previous promise of resolve,Just changepromiseThe status is resolve。
  // Soxhrdefined in CancelToken.promise.thenThe method will be executed, therebyxhrThe request will be canceled internally
  executor(function cancel(message) {
    // Determine whether the request has been canceled,Avoid multiple executions
    if (token.reason) {
      return;
    }
    token.reason = new Cancel(message);
    resolvePromise(token.reason);
  });
}

CancelToken.source = function source() {
  // source The method returns a CancelToken Example,with direct use new CancelToken It's the same operation
  var cancel;
  var token = new CancelToken(function executor(c) {
    cancel = c;
  });
  // Return the created CancelToken Examples and cancellation methods
  return {
    token: token,
    cancel: cancel
  };
}; 

The action that canceled the request was actually matched by a response in xhr.js

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

In CancelToken, the function controls the state of promise through the transfer and execution of the function.

From https://blog.csdn.net/weixin_50367873/article/details/135642707,If there is any infringement,Please contact to delete。