axios source code walkthrough

Axios is for fetching data from server and in this story I will first walkthrough the repo, then bring out detailed information for the main feature.

Of course, I will not go through each line of code…

axios instance

After axios is imported, we have 2 ways to fetch data, axios[method] or axios.create then axios[method]

Let’s take a look what happened there in the code.

/lib/axios.js

'use strict';
// helper functionsvar utils = require('./utils');var bind = require('./helpers/bind');var Axios = require('./core/Axios');var mergeConfig = require('./core/mergeConfig');var defaults = require('./defaults');/*** Create an instance of Axios** @param {Object} defaultConfig The default config for the instance* @return {Axios} A new instance of Axios*/function createInstance(defaultConfig) {// use default config to create axios instancevar context = new Axios(defaultConfig);// assign this of Axios.prototype.request to axios instance createdvar instance = bind(Axios.prototype.request, context);// Copy axios.prototype to instanceutils.extend(instance, Axios.prototype, context);// Copy context to instanceutils.extend(instance, context);return instance;}// Create the default instance to be exported// this is actually a function named wrap and has all properties of axios instance and it's propertyvar axios = createInstance(defaults);// Expose Axios class to allow class inheritanceaxios.Axios = Axios;// Factory for creating new instances// this is for custom axios configaxios.create = function create(instanceConfig) {return createInstance(mergeConfig(axios.defaults, instanceConfig));};// Expose Cancel & CancelTokenaxios.Cancel = require('./cancel/Cancel');axios.CancelToken = require('./cancel/CancelToken');axios.isCancel = require('./cancel/isCancel');// Expose all/spreadaxios.all = function all(promises) {return Promise.all(promises);};axios.spread = require('./helpers/spread');// Expose isAxiosErroraxios.isAxiosError = require('./helpers/isAxiosError');module.exports = axios;// Allow use of default import syntax in TypeScriptmodule.exports.default = axios;

This is where the axios import from!

We can see that the axiosexports is created by createInstance function and besides, axios.create is actually invoking createInstance and passing additional configs, we can get it from reading the codes literally.

Let’s dive into createInstance .

First, I need to explain the two helper function bind & extend .

bind

function bind(fn, thisArg) {  return function wrap() {    var args = new Array(arguments.length);    for (var i = 0; i < args.length; i++) {    args[i] = arguments[i];  }    return fn.apply(thisArg, args);  };}

Please remember this wrap function and we can see that it’s used to call a function with an assigned this .

You might wonder why it’s needed to convert arguments to an array, because apply can support array-like param. And from my investigation, this is because that feature is only available for moder browser and if you are using IE8, it’s not OK!

extend

function extend(a, b, thisArg) {  forEach(b, function assignValue(val, key) {    if (thisArg && typeof val === 'function') {      a[key] = bind(val, thisArg);    } else {      a[key] = val;    }    });  return a;}

ForEach here is for traversing each property of object b and call assignValue .

As a result, this helper will copy each property(own property) of b to a.

OK! Let’s get back to createInstance and we can clearly see what it does:

  1. Create a Axios instance(context)

So we now figured it out that the instance we created has all properties of axios instance and also it’s prototype!

axios instance object
axios instance object
axios instance object

Let’s first dive into Axios.js

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

Two things it does:

  1. Save default configs

Scrolling down, we can see the prototype method: request . It’s such a long function so I won’t just paste it all and only main story I will tell. And before that, we need to talk about interceptors.

Let’s go to InterceptorManager.js

function InterceptorManager() {  this.handlers = [];}

First, we can infer that all interceptors will be stored in a array, be they request or response.

And each time we want to add a new interceptor, we use function use to do that, resulting in one fulfilled & rejected function pair will be pushed.

Ok, let’s go back.

For request method, It do not directly make request, which will be handled by dispatchRequest , but it actually leads making the request.

Let’s get closer to the code and start from this line:

// filter out skipped interceptorsvar requestInterceptorChain = [];

And stop here, scroll down a little to see:

var chain = [dispatchRequest, undefined];

As you can imagine, there is also a chain for response:

var responseInterceptorChain = []

To sum up, 3 chain is created.

So will we connect these chains? Yes, we will. But first for the request chain,

making request chain
making request chain
making request chain

Each request interceptor object will be traversed and it’s fulfilled and rejected will be unshift into the chain in the form of two as a pair in one time.

And we will do the same thing to response chain. visually,

[fulfilled1, rejected1, fulfilled2, reject2]

such shape will be created.

Then the 3 different chain will be connected together!

[...requestChain, dispatchRequest, ...responseChain]

And into a promise which will be returned, these functions in the array will be then one by one.

This is why we can axios().then().catch() .

Other prototype methods

For each method , it will use request directly, that’s why we can ‘axios.get’.

detailed information

After figured out how the libray works, we can talk about something deeper. Let’s start from making request.

Making request

We have researched request , so what about dispatchRequest ?

Let’s go to dispatchRequest.js.

From this line:

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

we are pretty sure that adapter is something used to make request and yes it is. And due to my lack of knowledge in node.js, I will not cover anything about making requests in that environment.

In xhr.js file, at the top level you can see that it returns promise of course, fitting in the line of code we just referred and everything else is like using a vanilla XHR.

After XHR get the response, there will be checking on whether the request has been canceled, and after that the response will be processed by transformData .

If you have never passed such config, the default method will be used which is defined in defaults.js and two things we should particularly pay attention are that:

transformRequest: [function transformRequest(data, headers) {...
if (utils.isObject(data) || (headers && headers['Content-Type'] === 'application/json')) {
setContentTypeIfUnset(headers, 'application/json'); return JSON.stringify(data);}transformResponse: [function transformResponse(data) { if (strictJSONParsing || (forcedJSONParsing && utils.isString(data) && data.length)) { try { return JSON.parse(data); }

Normally, we will pass a object as param in request and server will reponse with JSON. But axios is pretty thoughtful to serilized our request param and parsed the response data.

And after:

return response;

other response interceptors can process the response data sequentially.

Cancellation

I am not sure there are many people will use this feature but it’s based on xhr.abort() , that’s its nature!

First, please go to Github and take a look at how it’s being used:

then let me show you what the token actually is:

you see? It just an instance of CancelToken with one property promise and let’s take a look what it will look like after it’s canceled:

wow! It has a reason!

In /lib/axios.js,

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

here’s when the functions are set in axios instance.

For CancelToken, let’s first check out CancelToken.source

CancelToken.source = function source() {  var cancel;  var token = new CancelToken(function executor(c) {    cancel = c;  });  return {    token: token,    cancel: cancel  };};

here, token is created by CancenToken function and the cancel function is something decided in the param function, so in this consturctor,

and in function CancelToken,

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;  executor(function cancel(message) {    if (token.reason) {    // Cancellation has already been requested    return;  }  token.reason = new Cancel(message);  resolvePromise(token.reason);  });}

the executor param is that function we passed in,

and the main flow is that:

new a promise & new a empty object and make it as the token,

call the param function(we already known that the cancel function in it will be the cancel function in CancelToken.source).

In the cancel function, the promise will be resolved and and reason will be assigned to the token object! That’s why when the request is cancelled, there would be a reason!