Learn the overall structure of axios source code and create your own request library

Introduction
The article introduces  axios the debugging method in detail. axios The implementation of functions such as constructors, interceptors, and cancellations are introduced in detail  . Finally, other request libraries are compared.

The version studied in this article is v0.19.0. Clone fork of the official repository master. As of now (December 14, 2019), the latest commitis 2019-12-09 15:52 ZhaoXC dc4bc49673943e352, fix: fix ignore set withCredentials false (#2582).

The warehouse of this article is here Ruochuan's axios-analysis github warehouse. Ask for one star.

If you are a job seeker and the project has been written and applied axios, the interviewer may ask you:

1. Why  axios can it be used as a function call or as an object, such as axios({}), axios.get.
2. Briefly describe  axios the calling process.
3. Have you ever used an interceptor? What is the principle?
4. Is there axiosa cancel function for use? How is it achieved?
5. Why do you support sending requests in the browser and also support nodesending requests?
Questions like this.

Chrome and vscode debugging axios source code method

Not long ago, the author answered a question on Zhihu. What should I do if the front-end cannot understand the source code of the front-end framework within a year? Some materials are recommended, and the reading volume is not bad. If you are interested, you can take a look. There are four main points:

1. With the help of debugging
2. Search and read relevant high praise articles
3. Write down what you don’t understand, and consult relevant documents
4. Summary

Looking at the source code, debugging is very important, so the author wrote down  axios the source code debugging method in detail to help some readers who may not know how to debug.

Chrome debugs axios in the browser environment

debugging method

axiosThere are files after packaging sourcemap.

# 可以克隆笔者的这个仓库代码
git clone https://github.com/lxchuan12/axios-analysis.git
cd axios-analaysis/axios
npm install
npm start
# open [http://localhost:3000](http://localhost:3000)
# chrome F12 source 控制面板  webpack//   .  lib 目录下,根据情况自行断点调试

axios/sandbox/client.htmlThis article is debugged through the above example .

By the way, let me briefly mention the debugging exampleexample. Although this part was written at the beginning of the article, it was deleted later, but I still write it down after thinking about it.

Find the file axios/examples/server.jsand modify the code as follows:

server = http.createServer(function (req, res) {
  var url = req.url;
  // 调试 examples
  console.log(url);
  // Process axios itself
  if (/axios\.min\.js$/.test(url)) {
    // 原来的代码 是 axios.min.js
    // pipeFileToResponse(res, '../dist/axios.min.js', 'text/javascript');
    pipeFileToResponse(res, '../dist/axios.js', 'text/javascript');
    return;
  }
  // 原来的代码 是 axios.min.map
  // if (/axios\.min.map$/.test(url)) {
  if (/axios\.map$/.test(url)) {
    // 原来的代码 是 axios.min.map
    // pipeFileToResponse(res, '../dist/axios.min.map', 'text/javascript');
    pipeFileToResponse(res, '../dist/axios.map', 'text/javascript');
    return;
  }
}
# 上述安装好依赖后
# npm run examples 不能同时开启,默认都是3000端口
# 可以指定端口 5000
# npm run examples ===  node ./examples/server.js
node ./examples/server.js -p 5000

Open http://localhost:5000, and then you can happily debug the examples Chromein the browser .examples

axios It supports  node the environment to send requests. Next, let's see how to use  vscode the debugging  node environment axios.

vscode debugs axios in the node environment

axios-analysis/Create a file in the root directory  .vscode/launch.jsonas follows:

{
    // 使用 IntelliSense 了解相关属性。
    // 悬停以查看现有属性的描述。
    // 欲了解更多信息,请访问: https://go.microsoft.com/fwlink/?linkid=830387
    "version": "0.2.0",
    "configurations": [
        {
            "type": "node",
            "request": "launch",
            "name": "Launch Program",
            "program": "${workspaceFolder}/axios/sandbox/client.js",
            "skipFiles": [
                "<node_internals>/**"
            ]
        },
    ]
}

Just press F5start debugging, according to your own situation, step over (F10), step through (F11)breakpoint debugging.

In fact, open source projects generally have a contribution guide axios/CONTRIBUTING.md, and the author just modified this guide to make the referenced sourcemapfiles debuggable.

First look at the structure of axios

git clone https://github.com/lxchuan12/axios-analysis.git
cd axios-analaysis/axios
npm install
npm start

According to the debugging method mentioned above,  npm start finally, debug directly in  chrome the browser. Open http://localhost:3000 and print it out on the console axios. It is estimated that many people have not printed it out.

console.log({axios: axios});

To see axios what the structure is like layer by layer, first have a general impression.

The author drew a more detailed diagram.

// package.json
{
  "name": "axios",
  "version": "0.19.0",
  "description": "Promise based HTTP client for the browser and node.js",
  "main": "index.js",
  // ...
}

After reading the structure diagram, if you look at jQuery, underscoreand lodashsource code, you will find that it is actually axiossimilar to the source code design.

jQuery Aliases  $, underscore loadsh aliases  _ are both functions and objects. Such as jQueryusage. $('#id')$.ajax.

Next, look at the implementation of the specific source code. You can debug with breakpoints.

Breakpoint debugging essentials:
The assignment statement can be skipped in one step, just look at the return value, and see it in detail later.
Function execution needs to be followed by breakpoints, and it can also be combined with comments and context to reverse what the function does.

Axios source code initialization

Look at the source code The first step is to look at it first package.json. Generally, the main entry file will be declared  main .

// package.json
{
  "name": "axios",
  "version": "0.19.0",
  "description": "Promise based HTTP client for the browser and node.js",
  "main": "index.js",
  // ...
}

 main entry file

// index.js
module.exports = require('./lib/axios');

lib/axios.jsmain file

axios.jsThere are relatively many file codes. The narrative is divided into three parts.

  1. The first part: introduce some utility functions utils, Axiosconstructors, default configurations, defaultsetc.

  2. The second part: is to generate instance objects  axios, axios.Axios, axios.createand so on.

  3. The third part cancels related API implementations, as well as implementations of all, spread, and export.

first part

Introduce some utility functions utils, Axiosconstructors, default configuration, defaultsetc.

// 第一部分:
// lib/axios
// 严格模式
'use strict';
// 引入 utils 对象,有很多工具方法。
var utils = require('./utils');
// 引入 bind 方法
var bind = require('./helpers/bind');
// 核心构造函数 Axios
var Axios = require('./core/Axios');
// 合并配置方法
var mergeConfig = require('./core/mergeConfig');
// 引入默认配置
var defaults = require('./defaults');

the second part

is to generate instance objects  axios, , axios.Axios, axios.createetc.

/**
 * Create an instance of Axios
 *
 * @param {Object} defaultConfig The default config for the instance
 * @return {Axios} A new instance of Axios
 */
function createInstance(defaultConfig) {
  // new 一个 Axios 生成实例对象
  var context = new Axios(defaultConfig);
  // bind 返回一个新的 wrap 函数,
  // 也就是为什么调用 axios 是调用 Axios.prototype.request 函数的原因
  var instance = bind(Axios.prototype.request, context);
  // Copy axios.prototype to instance
  // 复制 Axios.prototype 到实例上。
  // 也就是为什么 有 axios.get 等别名方法,
  // 且调用的是 Axios.prototype.get 等别名方法。
  utils.extend(instance, Axios.prototype, context);
  // Copy context to instance
  // 复制 context 到 intance 实例
  // 也就是为什么默认配置 axios.defaults 和拦截器  axios.interceptors 可以使用的原因
  // 其实是new Axios().defaults 和 new Axios().interceptors
  utils.extend(instance, context);
  // 最后返回实例对象,以上代码,在上文的图中都有体现。这时可以仔细看下上图。
  return instance;
}

// Create the default instance to be exported
// 导出 创建默认实例
var axios = createInstance(defaults);
// Expose Axios class to allow class inheritance
// 暴露 Axios class 允许 class 继承 也就是可以 new axios.Axios()
// 但  axios 文档中 并没有提到这个,我们平时也用得少。
axios.Axios = Axios;

// Factory for creating new instances
// 工厂模式 创建新的实例 用户可以自定义一些参数
axios.create = function create(instanceConfig) {
  return createInstance(mergeConfig(axios.defaults, instanceConfig));
};

Here is a brief description of the factory model. axios.create, that is, the user does not need to know how it is implemented internally.
To give an example from life, when we buy a mobile phone, we don't need to know how the mobile phone is made, it is the factory model.
After reading the second part, several tool functions are involved, such as bind, extend. These tools and methods are described next.

Bind of tool method

axios/lib/helpers/bind.js

'use strict';
// 返回一个新的函数 wrap
module.exports = function bind(fn, thisArg) {
  returnfunction wrap() {
    var args = newArray(arguments.length);
    for (var i = 0; i < args.length; i++) {
      args[i] = arguments[i];
    }
    // 把 argument 对象放在数组 args 里
    return fn.apply(thisArg, args);
  };
};

Pass two parameters function and thisArgpointer.
Generate an array of parameters arguments, and finally call to return the parameter structure. In fact , such array-like objects are
now  apply supported  , and there is no need to manually convert the array. So why does the author want to convert the array, for performance? Not supported at the time? Or is it that the author doesn't know? This is unknown. Readers who know are welcome to tell the author in the comment area.arguments

For readers who are not very familiar with apply, , calland etc., you can read another article by the author . The interviewer asked: Can you simulate the bind method of JSbind面试官问系列

for example

function fn(){
  console.log.apply(console, arguments);
}
fn(1,2,3,4,5,6, '若川');
// 1 2 3 4 5 6 '若川'

utils.extend of tool method

axios/lib/utils.js

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;
}

In fact, it is to traverse the parameter  b object, copy it to  a the object, and call it if it is a function  bind .

utils.forEach of the tool method

axios/lib/utils.js

Iterate over arrays and objects. The design pattern is called the iterator pattern. Many source codes have traversal functions like this. As we all know jQuery $.each.

/**
 * @param {Object|Array} obj The object to iterate
 * @param {Function} fn The callback to invoke for each item
 */
function forEach(obj, fn) {
  // Don't bother if no value provided
  // 判断 null 和 undefined 直接返回
  if (obj === null || typeof obj === 'undefined') {
    return;
  }

  // Force an array if not already something iterable
  // 如果不是对象,放在数组里。
  if (typeof obj !== 'object') {
    /*eslint no-param-reassign:0*/
    obj = [obj];
  }

  // 是数组 则用for 循环,调用 fn 函数。参数类似 Array.prototype.forEach 的前三个参数。
  if (isArray(obj)) {
    // Iterate over array values
    for (var i = 0, l = obj.length; i < l; i++) {
      fn.call(null, obj[i], i, obj);
    }
  } else {
    // Iterate over object keys
    // 用 for in 遍历对象,但 for in 会遍历原型链上可遍历的属性。
    // 所以用 hasOwnProperty 来过滤自身属性了。
    // 其实也可以用Object.keys来遍历,它不遍历原型链上可遍历的属性。
    for (var key in obj) {
      if (Object.prototype.hasOwnProperty.call(obj, key)) {
        fn.call(null, obj[key], key, obj);
      }
    }
  }
}

If you are not familiar with Objectthe relevant information API, you can check out an article I wrote before. JavaScript Object All API Parsing

the third part

Cancel related API implementations, as well as implementations of all, spread, and export.

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

// Expose all/spread
// 导出 all 和 spread API
axios.all = function all(promises) {
  returnPromise.all(promises);
};
axios.spread = require('./helpers/spread');

module.exports = axios;

// Allow use of default import syntax in TypeScript
// 也就是可以以下方式引入
// import axios from 'axios';
module.exports.default = axios;

Introduced here  spread, the canceled ones APIwill not be analyzed for the time being, and will be analyzed in detail later.

Suppose you have such a need.

function f(x, y, z) {}
var args = [1, 2, 3];
f.apply(null, args);

Then you can use spreadthe method. usage:

axios.spread(function(x, y, z) {})([1, 2, 3]);
/**
 * @param {Function} callback
 * @returns {Function}
 */
module.exports = function spread(callback) {
  returnfunction wrap(arr) {
    return callback.apply(null, arr);
  };
};

Above var context = new Axios(defaultConfig);, the core constructor is introduced next Axios.

Core Constructor Axios

axios/lib/core/Axios.js

constructor Axios.

function Axios(instanceConfig) {
  // 默认参数
  this.defaults = instanceConfig;
  // 拦截器 请求和响应拦截器
  this.interceptors = {
    request: new InterceptorManager(),
    response: new InterceptorManager()
  };
}
Axios.prototype.request = function(config){
  // 省略,这个是核心方法,后文结合例子详细描述
  // code ...
  var promise = Promise.resolve(config);
  // code ...
  return promise;
}
// 这是获取 Uri 的函数,这里省略
Axios.prototype.getUri = function(){}
// 提供一些请求方法的别名
// Provide aliases for supported request methods
// 遍历执行
// 也就是为啥我们可以 axios.get 等别名的方式调用,而且调用的是 Axios.prototype.request 方法
// 这个也在上面的 axios 结构图上有所体现。
utils.forEach(['delete', 'get', 'head', 'options'], function forEachMethodNoData(method) {
  /*eslint func-names:0*/
  Axios.prototype[method] = function(url, config) {
    returnthis.request(utils.merge(config || {}, {
      method: method,
      url: url
    }));
  };
});

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

module.exports = Axios;

Next look at the interceptor section.

Interceptor management constructor InterceptorManager

Intercept before request, and intercept after request.
It is used in Axios.prototype.requestthe function, and how to implement the interception will be described in detail later with examples.

axios github repository interceptor documentation

how to use:

// Add a request interceptor
// 添加请求前拦截器
axios.interceptors.request.use(function (config) {
  // Do something before request is sent
  return config;
}, function (error) {
  // Do something with request error
  returnPromise.reject(error);
});

// Add a response interceptor
// 添加请求后拦截器
axios.interceptors.response.use(function (response) {
  // Any status code that lie within the range of 2xx cause this function to trigger
  // Do something with response data
  return response;
}, function (error) {
  // Any status codes that falls outside the range of 2xx cause this function to trigger
  // Do something with response error
  returnPromise.reject(error);
});

If you want to put the interceptor, you can use ejectthe method.

const myInterceptor = axios.interceptors.request.use(function () {/*...*/});
axios.interceptors.request.eject(myInterceptor);

Interceptors can also be added on custom instances.

const instance = axios.create();
instance.interceptors.request.use(function () {/*...*/});

Source implementation:

Constructor handles to store interceptor functions.

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

Next, three methods are declared: use, remove, and traverse.

Using InterceptorManager.prototype.use

Pass two functions as parameters, and store one item in the array {fulfilled: function(){}, rejected: function(){}}. Returns a number  ID, used to remove the interceptor.

/**
 * @param {Function} fulfilled The function to handle `then` for a `Promise`
 * @param {Function} rejected The function to handle `reject` for a `Promise`
 *
 * @return {Number} 返回ID 是为了用 eject 移除
 */
InterceptorManager.prototype.use = function use(fulfilled, rejected) {
  this.handlers.push({
    fulfilled: fulfilled,
    rejected: rejected
  });
  returnthis.handlers.length - 1;
};

InterceptorManager.prototype.eject removed

 Remove the interceptor based on  use the returned ID

/**
 * @param {Number} id The ID that was returned by `use`
 */
InterceptorManager.prototype.eject = function eject(id) {
  if (this.handlers[id]) {
    this.handlers[id] = null;
  }
};

A bit similar to timer sum setTimeout ,  setIntervalthe return value is id. Use clearTimeout and clearIntervalto clear the timer.

// 提一下 定时器回调函数是可以传参的,返回值 timer 是数字
var timer = setInterval((name) => {
  console.log(name);
}, 1000, '若川');
console.log(timer); // 数字 ID
// 在控制台等会再输入执行这句,定时器就被清除了
clearInterval(timer);

InterceptorManager.prototype.forEach Traversal

Traverse and execute all interceptors, pass a callback function (each interceptor function as a parameter) call, the item that is removed is, nullso it will not be executed, and the effect of removal is achieved.

/**
 * @param {Function} fn The function to call for each interceptor
 */
InterceptorManager.prototype.forEach = function forEach(fn) {
  utils.forEach(this.handlers, function forEachHandler(h) {
    if (h !== null) {
      fn(h);
    }
  });
};

instance binding

The debugging-time operation described above npm start uses axios/sandbox/client.htmlthe path file as an example, and readers can debug by themselves.

Below is a piece of code in this file.

axios(options)
.then(function (res) {
  response.innerHTML = JSON.stringify(res.data, null, 2);
})
.catch(function (res) {
  response.innerHTML = JSON.stringify(res.data, null, 2);
});

First look at the call stack process

If you don't want to debug step by step, there is a tricky way.
I know that  axios I have used it XMLHttpRequest.
Can be searched in the project: new XMLHttpRequest.
Locate the file   and set a breakpoint in the browser to debug axios/lib/adapters/xhr.js
this statement  , and then look at the implementation of specific functions according to the call stack.var request = new XMLHttpRequest();
chrome

Call Stack

dispatchXhrRequest (xhr.js:19)
xhrAdapter (xhr.js:12)
dispatchRequest (dispatchRequest.js:60)
Promise.then (async)
request (Axios.js:54)
wrap (bind.js:10)
submit.onclick ((index):138)

Briefly describe the process:

  1. Send Request button click submit.onclick

  2. Calling  axios a function is actually calling  Axios.prototype.request a function, and this function uses  bind a returned wrapfunction named .

  3. transfer Axios.prototype.request

  4. (Execute the request interceptor when there is a request interceptor), and  dispatchRequestthe method will be executed in the middle

  5. dispatchRequest after calling adapter (xhrAdapter)

  6. Promise The function in the last call  dispatchXhrRequest, (if there is a response interceptor, the response interceptor will be called at the end)

If you read the beginning of the article carefully axios 结构关系图, you actually have a general understanding of this process.

Next, let's look at  Axios.prototype.request the specific implementation.

Axios.prototype.request request core method

This function is the core function. Mainly did the following things:

1. Judging that the first parameter is a string, set the url, that is, support axios('example/url', [, config]), and also support axios({}).
2. Combine the default parameters and the parameters passed by the user.
3. Set the method of the request. The default is getthe method
. 4. Combine the request and response interceptors set by the user, and send the request to form a dispatchRequestchain Promise, and finally return Promisethe instance.

That is to say, it is guaranteed that the interceptor is executed before the request, then the request is sent, and the interceptor is executed in response to this order. <br>
That is why `then` and `catch` methods are still available in the end. <br>

Axios.prototype.request = function request(config) {
  /*eslint no-param-reassign:0*/
  // Allow for axios('example/url'[, config]) a la fetch API
  // 这一段代码 其实就是 使 axios('example/url', [, config])
  // config 参数可以省略
  if (typeof config === 'string') {
    config = arguments[1] || {};
    config.url = arguments[0];
  } else {
    config = config || {};
  }

  // 合并默认参数和用户传递的参数
  config = mergeConfig(this.defaults, config);

  // Set config.method
  // 设置 请求方法,默认 get 。
  if (config.method) {
    config.method = config.method.toLowerCase();
  } elseif (this.defaults.method) {
    config.method = this.defaults.method.toLowerCase();
  } else {
    config.method = 'get';
  }
  // Hook up interceptors middleware
  // 组成`Promise`链 这段拆开到后文再讲述
};

 Form Promisea chain and return Promisean instance

This part: the request and response interceptors set by the user, and dispatchRequestthe composition Promisechain of sending requests. That is to say, it is guaranteed that the interceptor executes before the request, then sends the request, and then responds to the interceptor to execute such order

也就是保证了请求前拦截器先执行,然后发送请求,再响应拦截器执行这样的顺序<br>
也就是为啥最后还是可以`then`,`catch`方法的缘故。<br>

If readers are Promisenot familiar with it, it is recommended to read Mr. Ruan's book "Introduction to ES6 Standards". Ruan Yifeng's ES6 Promise-resolve and JavaScript Promise mini-book (Chinese version)

// 组成`Promise`链
  // Hook up interceptors middleware
  // 把 xhr 请求 的 dispatchRequest 和 undefined 放在一个数组里
  var chain = [dispatchRequest, undefined];
  // 创建 Promise 实例
  var promise = Promise.resolve(config);

 // 遍历用户设置的请求拦截器 放到数组的 chain 前面
  this.interceptors.request.forEach(function unshiftRequestInterceptors(interceptor) {
    chain.unshift(interceptor.fulfilled, interceptor.rejected);
  });

 // 遍历用户设置的响应拦截器 放到数组的 chain 后面
  this.interceptors.response.forEach(function pushResponseInterceptors(interceptor) {
    chain.push(interceptor.fulfilled, interceptor.rejected);
  });

 // 遍历 chain 数组,直到遍历 chain.length 为 0
  while (chain.length) {
    // 两两对应移出来 放到 then 的两个参数里。
    promise = promise.then(chain.shift(), chain.shift());
  }

  return promise;
var promise = Promise.resolve(config);

Explain this sentence. The role is to generate Promiseinstances.

var promise = Promise.resolve({name: '若川'})
// 等价于
// new Promise(resolve => resolve({name: '若川'}))
promise.then(function (config){
  console.log(config)
});
// {name: "若川"}

Also explain what will appear later Promise.reject(error);:

Promise.reject(error);
var promise = Promise.reject({name: '若川'})
// 等价于
// new Promise(reject => reject({name: '若川'}))

// promise.then(null, function (config){
//   console.log(config)
// });
// 等价于
promise.catch(function (config){
  console.log(config)
});
// {name: "若川"}

Let's understand this code with an example.
Unfortunately, examplethere are no interceptor examples in the folder. The author added an example of an interceptor on the basis of exampleZhongji . , which is convenient for readers to debug.example/getaxios/examples/interceptors

node ./examples/server.js -p 5000

promise = promise.then(chain.shift(), chain.shift());This code hits a breakpoint.

You will get a picture like this.

localPay special attention to the array in the bottom, right chain. That is the structure.

var chain = [
  '请求成功拦截2', '请求失败拦截2',
  '请求成功拦截1', '请求失败拦截1',
  dispatch,  undefined,
  '响应成功拦截1', '响应失败拦截1',
  '响应成功拦截2', '响应失败拦截2',
]

 This code is relatively convoluted. That is, code similar to the following will be generated, and dispatchRequestthe method will be called in the middle

// config 是 用户配置和默认配置合并的
var promise = Promise.resolve(config);
promise.then('请求成功拦截2', '请求失败拦截2')
.then('请求成功拦截1', '请求失败拦截1')
.then(dispatchRequest, undefined)
.then('响应成功拦截1', '响应失败拦截1')
.then('响应成功拦截2', '响应失败拦截2')

.then('用户写的业务处理函数')
.catch('用户写的报错业务处理函数');

Here is promise thenthe catchknowledge:
Promise.prototype.thenthe first parameter of the method is resolvedthe callback function of the state, and the second parameter (optional) is rejectedthe callback function of the state. So it comes in pairs.
Promise.prototype.catchmethod is an alias for .then(null, rejection)or .then(undefined, rejection)that specifies a callback function when an error occurs.
thenThe method returns a new Promiseinstance (note, not the original Promiseinstance). Therefore, chain writing can be used, that is, thenanother method is called after the method then.

Combined with the above example in more detail, the code is like this.

var promise = Promise.resolve(config);
// promise.then('请求成功拦截2', '请求失败拦截2')
promise.then(function requestSuccess2(config) {
  console.log('------request------success------2');
  return config;
}, function requestError2(error) {
  console.log('------response------error------2');
  returnPromise.reject(error);
})

// .then('请求成功拦截1', '请求失败拦截1')
.then(function requestSuccess1(config) {
  console.log('------request------success------1');
  return config;
}, function requestError1(error) {
  console.log('------response------error------1');
  returnPromise.reject(error);
})

// .then(dispatchRequest, undefined)
.then( function dispatchRequest(config) {
  /**
    * 适配器返回的也是Promise 实例
      adapter = function xhrAdapter(config) {
            return new Promise(function dispatchXhrRequest(resolve, reject) {})
      }
    **/
  return adapter(config).then(function onAdapterResolution(response) {
    // 省略代码 ...
    return response;
  }, function onAdapterRejection(reason) {
    // 省略代码 ...
    returnPromise.reject(reason);
  });
}, undefined)

// .then('响应成功拦截1', '响应失败拦截1')
.then(function responseSuccess1(response) {
  console.log('------response------success------1');
  return response;
}, function responseError1(error) {
  console.log('------response------error------1');
  returnPromise.reject(error);
})

// .then('响应成功拦截2', '响应失败拦截2')
.then(function responseSuccess2(response) {
  console.log('------response------success------2');
  return response;
}, function responseError2(error) {
  console.log('------response------error------2');
  returnPromise.reject(error);
})

// .then('用户写的业务处理函数')
// .catch('用户写的报错业务处理函数');
.then(function (response) {
  console.log('哈哈哈,终于获取到数据了', response);
})
.catch(function (err) {
  console.log('哎呀,怎么报错了', err);
});

Look carefully at this Promisechain call, the code is similar. thenThe last parameter returned by the method is thenthe first parameter of the next method.
catchAll error captures are returned Promise.reject(error), which is for the convenience catchof users to catch errors.

for example:

var p1 = newPromise((resolve, reject) => {
 reject(newError({name: '若川'}));
});

p1.catch(err => {
    console.log(res, 'err');
    returnPromise.reject(err)
})
.catch(err => {
 console.log(err, 'err1');
})
.catch(err => {
 console.log(err, 'err2');
});

err2It will not be captured, that is, it will not be executed, but if it returns return Promise.reject(err), it can be captured.

Finally, draw a picture to summarize  Promise the chain call.

Summary: 1. Interceptors for requests and responses can be written Promise.

  1. If multiple request responders are set, the one set later will be executed first.

  2. If multiple response interceptors are set, the one set first will be executed first.

dispatchRequest(config) Here configis what the request success interceptor returns. Next look at dispatchRequestthe function.

dispatchRequest final dispatch request

This function mainly does the following things:

1. If it has been canceled,  throw the reason will be reported as an error and the direction will be Promisechanged rejected.
2. Ensure  config.header existence.
3. Convert data using user-set and default request converters.
4. Slap flat  config.header.
5. Delete some  config.header.
6. Return the instance after the execution of the adapter adapter( Promiseinstance)  thenafter execution  Promise. The returned result is passed to the response interceptor for processing.

 

'use strict';
// utils 工具函数
var utils = require('./../utils');
// 转换数据
var transformData = require('./transformData');
// 取消状态
var isCancel = require('../cancel/isCancel');
// 默认参数
var defaults = require('../defaults');

/**
 * 抛出 错误原因,使`Promise`走向`rejected`
 */
function throwIfCancellationRequested(config) {
  if (config.cancelToken) {
    config.cancelToken.throwIfRequested();
  }
}

/**
 * Dispatch a request to the server using the configured adapter.
 *
 * @param {object} config The config that is to be used for the request
 * @returns {Promise} The Promise to be fulfilled
 */
module.exports = function dispatchRequest(config) {
  // 取消相关
  throwIfCancellationRequested(config);

  // Ensure headers exist
  // 确保 headers 存在
  config.headers = config.headers || {};

  // Transform request data
  // 转换请求的数据
  config.data = transformData(
    config.data,
    config.headers,
    config.transformRequest
  );

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

  // 以下这些方法 删除 headers
  utils.forEach(
    ['delete', 'get', 'head', 'post', 'put', 'patch', 'common'],
    function cleanHeaderConfig(method) {
      delete config.headers[method];
    }
  );
  // adapter 适配器部分 拆开 放在下文讲
};

transformData of dispatchRequest transforms data

There is a function in the above code  transformData , which is explained here. In fact, it traverses the passed function array to operate on the data, and finally returns the data.

axios.defaults.transformResponse There is a function in the array by default, so use concatthe linked custom function.

use:

file pathaxios/examples/transform-response/index.html

This code is actually converting a string in the time format into a time object, which can be called directly getMonth.

var ISO_8601 = /(\d{4}-\d{2}-\d{2})T(\d{2}:\d{2}:\d{2})Z/;
function formatDate(d) {
  return (d.getMonth() + 1) + '/' + d.getDate() + '/' + d.getFullYear();
}

axios.get('https://api.github.com/users/mzabriskie', {
  transformResponse: axios.defaults.transformResponse.concat(function (data, headers) {
    Object.keys(data).forEach(function (k) {
      if (ISO_8601.test(data[k])) {
        data[k] = newDate(Date.parse(data[k]));
      }
    });
    return data;
  })
})
.then(function (res) {
  document.getElementById('created').innerHTML = formatDate(res.data.created_at);
});

source code:

data It is to traverse the array, call the transfer and  headers parameter call function in the array  .

module.exports = function transformData(data, headers, fns) {
  /*eslint no-param-reassign:0*/
  utils.forEach(fns, function transform(fn) {
    data = fn(data, headers);
  });

  return data;
};

The adapter adapter execution part of dispatchRequest

Adapter is called adapter pattern in design pattern. Let me tell you a simple example in life, it will be easy for everyone to understand.

We often use round holes for the headphone jacks of mobile phones before, but now the headphone jack and the charging interface are basically combined into one. unified as typec.

At this time we need one typec转圆孔的转接口, which is the adapter.

// adapter 适配器部分
  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
        );
      }
    }

    returnPromise.reject(reason);
  });

Let's look at the specifics next  adapter.

The adapter adapter actually sends the request

var adapter = config.adapter || defaults.adapter;

After reading the above  adapter, you can know that user customization is supported. wx.request For example, you can write one as required  through the WeChat applet  adapter.
Let's take a look  defaults.ddapter.
file path:axios/lib/defaults.js

Import according to the current environment, if it is imported in the browser environment xhr, it is nodeimported in the environment http.
A similar judgment nodeenvironment sentry-javascriptcan also be seen in the source code.

function getDefaultAdapter() {
  var adapter;
  // 根据 XMLHttpRequest 判断
  if (typeof XMLHttpRequest !== 'undefined') {
    // For browsers use XHR adapter
    adapter = require('./adapters/xhr');
    // 根据 process 判断
  } elseif (typeof process !== 'undefined' && Object.prototype.toString.call(process) === '[object process]') {
    // For node use HTTP adapter
    adapter = require('./adapters/http');
  }
  return adapter;
}
var defaults = {
  adapter: getDefaultAdapter(),
  // ...
};

xhr

Next comes  XMLHttpRequest the objects we are familiar with.

Readers who may not understand can refer to the XMLHttpRequest MDN document.

The main reminder: onabortit is a request cancellation event, withCredentialswhich is a boolean value used to specify  Access-Control whether the cross-domain request should have authorization information, such as  cookie or authorization  header header.

This piece of code has been deleted, you can refer to Ruochuan’s axios-analysiswarehouse for details, or you can clone the author’s axios-analysiswarehouse and then analyze it in detail during debugging.

module.exports = function xhrAdapter(config) {
  returnnewPromise(function dispatchXhrRequest(resolve, reject) {
    // 这块代码有删减
    var request = new XMLHttpRequest();
    request.open()
    request.timeout = config.timeout;
    // 监听 state 改变
    request.onreadystatechange = function handleLoad() {
      if (!request || request.readyState !== 4) {
        return;
      }
      // ...
    }
    // 取消
    request.onabort = function(){};
    // 错误
    request.onerror = function(){};
    // 超时
    request.ontimeout = function(){};
    // cookies 跨域携带 cookies 面试官常喜欢考这个
    // 一个布尔值,用来指定跨域 Access-Control 请求是否应带有授权信息,如 cookie 或授权 header 头。
    // Add withCredentials to request if needed
    if (!utils.isUndefined(config.withCredentials)) {
      request.withCredentials = !!config.withCredentials;
    }

    // 上传下载进度相关
    // Handle progress if needed
    if (typeof config.onDownloadProgress === 'function') {
      request.addEventListener('progress', config.onDownloadProgress);
    }

    // Not all browsers support upload events
    if (typeof config.onUploadProgress === 'function' && request.upload) {
      request.upload.addEventListener('progress', config.onUploadProgress);
    }

    // Send the request
    // 发送请求
    request.send(requestData);
  });
}

In fact  fetch , the support is very good now. Ali's open source umi-request request library is packaged fetchinstead of used XMLHttpRequest. At the end of the article, the difference between the next  umi-request and  the axios next is roughly described.

http

httpI won’t go into details here, interested readers can check Ruochuan’s axios-analysiswarehouse by themselves.

module.exports = function httpAdapter(config) {
  returnnewPromise(function dispatchHttpRequest(resolvePromise, rejectPromise) {
  });
};

There is a cancellation module above  dispatchRequest , which I think is the key point, so let’s talk about it in detail at the end:

Cancellation module of dispatchRequest

cancel tokenCancellation requests are available .

The axios cancel token API is a revocation based  promise cancellation proposal.

The axios cancel token API is based on the withdrawn cancelable promises proposal.

axios document cancellation

Both ways of usage are described in detail on the documentation.

Unfortunately, examplethere is no cancel example in the folder. The author added an example of cancellation on the basis of exampleZhongji . , which is convenient for readers to debug.example/getaxios/examples/cancel

node ./examples/server.js -p 5000

requestThe two modules of the interceptor in and dispatchthe cancellation in are relatively complex, and can be debugged and absorbed.

const CancelToken = axios.CancelToken;
const source = CancelToken.source();

axios.get('/get/server', {
  cancelToken: source.token
}).catch(function (err) {
  if (axios.isCancel(err)) {
    console.log('Request canceled', err.message);
  } else {
    // handle error
  }
});

// cancel the request (the message parameter is optional)
// 取消函数。
source.cancel('哎呀,我被若川取消了');

Cancel request module code example

Combined with the source code cancellation process, this is probably the case. This section is placed in the code axios/examples/cancel-token/index.html.

The parameter  config.cancelToken is generated source.cancel('哎呀,我被若川取消了');only when it is triggered.

// source.cancel('哎呀,我被若川取消了');
// 点击取消时才会 生成 cancelToken 实例对象。
// 点击取消后,会生成原因,看懂了这段在看之后的源码,可能就好理解了。
var config = {
  name: '若川',
  // 这里简化了
  cancelToken: {
        promise: newPromise(function(resolve){
            resolve({ message: '哎呀,我被若川取消了'})
        }),
        reason: { message: '哎呀,我被若川取消了' }
  },
};
// 取消 抛出异常方法
function throwIfCancellationRequested(config){
  // 取消的情况下执行这句
  if(config.cancelToken){
    //   这里源代码 便于执行,我改成具体代码
    // config.cancelToken.throwIfRequested();
    // if (this.reason) {
    //     throw this.reason;
    //   }
    if(config.cancelToken.reason){
        throw config.cancelToken.reason;
    }
  }
}

function dispatchRequest(config){
  // 有可能是执行到这里就取消了,所以抛出错误会被err2 捕获到
  throwIfCancellationRequested(config);
  //  adapter xhr适配器
  returnnewPromise((resovle, reject) => {
    var request = new XMLHttpRequest();
    console.log('request', request);
    if (config.cancelToken) {
        // Handle cancellation
        config.cancelToken.promise.then(function onCanceled(cancel) {
            if (!request) {
                return;
            }

            request.abort();
            reject(cancel);
            // Clean up request
            request = null;
        });
    }
  })
  .then(function(res){
    // 有可能是执行到这里就才取消 取消的情况下执行这句
    throwIfCancellationRequested(config);
    console.log('res', res);
    return res;
  })
  .catch(function(reason){
    // 有可能是执行到这里就才取消 取消的情况下执行这句
    throwIfCancellationRequested(config);
    console.log('reason', reason);
    returnPromise.reject(reason);
  });
}

var promise = Promise.resolve(config);

// 没设置拦截器的情况下是这样的
promise
.then(dispatchRequest, undefined)
// 用户定义的then 和 catch
.then(function(res){
  console.log('res1', res);
  return res;
})
.catch(function(err){
  console.log('err2', err);
  returnPromise.reject(err);
});
// err2 {message: "哎呀,我被若川取消了"}

Next, look at the source code of the cancel module

See how to pass generate config.cancelToken.

file path:

axios/lib/cancel/CancelToken.js

const CancelToken = axios.CancelToken;
const source = CancelToken.source();
source.cancel('哎呀,我被若川取消了');

Implementation seen by example  CancelToken.source,

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

The general structure after execution sourceis like this.

{
    token: {
    promise: newPromise(function(resolve){
      resolve({ message: '哎呀,我被若川取消了'})
    }),
    reason: { message: '哎呀,我被若川取消了' }
  },
  cancel: function cancel(message) {
    if (token.reason) {
      // Cancellation has already been requested
      // 已经取消
      return;
    }
    token.reason = {message: '哎呀,我被若川取消了'};
  }
}

keep watching new CancelToken

// CancelToken
// 通过 CancelToken 来取消请求操作
function CancelToken(executor) {
  if (typeof executor !== 'function') {
    thrownewTypeError('executor must be a function.');
  }

  var resolvePromise;
  this.promise = newPromise(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);
  });
}

module.exports = CancelToken;

This is used in the adapter that sends the request.

// xhr
if (config.cancelToken) {
  // Handle cancellation
  config.cancelToken.promise.then(function onCanceled(cancel) {
    if (!request) {
      return;
    }

    request.abort();
    reject(cancel);
    // Clean up request
    request = null;
  });
}

dispatchRequest The specific implementation in throwIfCancellationRequested: throw throws an exception.

// 抛出异常函数
function throwIfCancellationRequested(config) {
  if (config.cancelToken) {
    config.cancelToken.throwIfRequested();
  }
}
// 抛出异常 用户 { message: '哎呀,我被若川取消了' }
CancelToken.prototype.throwIfRequested = function throwIfRequested() {
  if (this.reason) {
    throwthis.reason;
  }
};

Cancel process call stack

1.source.cancel()
2.resolvePromise(token.reason);
3.config.cancelToken.promise.then(function onCanceled(cancel) {})

last entryrequest.abort();``reject(cancel);

The cancellation process is introduced here. The main thing is to pass the configuration parameters cancelToken, it will be generated when it is canceled cancelToken, and if it is judged, an error will be thrown, Promise so rejectedthat the user can capture the message {message: 'Cancellation information set by the user'}.

The article is written here and it is basically nearing the end.

If you can read to the end, it means that you have surpassed many people^_^

axiosIt is a very good request library, but it certainly cannot meet the needs of all developers. Next, compare other libraries to see what specific needs other developers have.

Compared to other request libraries

Ajax

Shuige, head of the FCC Chengdu community, open sourced KoAJAX.

How to hold a technical conference with open source software? An excerpt from the article below.

Front-end request library - KoAJAX The most commonly used HTTP request library for domestic front-end students should be axios, right? Although its Interceptor (interceptor) API is .use(), it is completely different from the middleware mode of Node.js Express, Koa and other frameworks. Compared with jQuery .ajaxPrefilter() and dataFilter(), there is no substantial improvement; upload , The download progress is simpler than jQuery.Deferred(), just two special callback options. Therefore, it still needs to memorize specific APIs for specific requirements, which is not concise enough.

Fortunately, in the process of researching how to use ES 2018 asynchronous iterators to implement a Koa-like middleware engine, Shuige made a more practical upper-level application - KoAJAX. Its entire execution process is based on Koa-style middleware, and it is itself a middleware call stack. In addition to the shortcut methods such as .get(), .post(), .put(), .delete() commonly used in RESTful API, developers only need to remember .use() and next(), and the others are ES standards Grammar and TS type deduction.

umi-request Ali open source request library

umi-request github repository

umi-request And  fetchaxios similarities and differences.

I have to say that umi-request it is really powerful, and interested readers can read its source code.

axiosOn the basis of understanding , umi-requestit should not be difficult to understand the source code.

For example,  umi-request the cancel module code is almost exactly axiosthe same as .

Summarize

The article describes  axios the debugging method in detail. axios The implementation of functions such as constructors, interceptors, and cancellations are introduced in detail  . Finally, other request libraries are compared.

Finally, draw a picture to summarize the overall process of axios.

Answer the questions posed at the beginning of the article:

If you are a job seeker and the project has been written and applied axios, the interviewer may ask you:

1. Why  axios can it be used as a function call or as an object, such as axios({}), axios.get.
Answer: axiosThe essence is a function, and some alias methods are assigned, such as get, postmethods, which can be called, and the final call is still Axios.prototype.requesta function.
2. Briefly describe  axios the calling process.
Answer: It is actually the method called Axios.prototype.request, and the final return is promisea chain call, and the actual request is dispatchRequestdispatched in .
3. Have you ever used an interceptor? What is the principle?
Answer: Yes, use axios.interceptors.request.usethe add request success and failure interceptor function, use axios.interceptors.response.usethe add response success and failure interceptor function. When Axios.prototype.requestfunctions form promisechain calls, Interceptors.protype.forEachtraversal request and response interceptors are added to dispatchRequestboth ends of the actual request, so as to achieve pre-request interception and post-response interception. Interceptors also support Interceptors.protype.ejectmethod removal.
4. Is there axiosa cancel function for use? How is it achieved? Answer: It has been used, and it can be canceled
by passing configthe configuration . cancelTokenIt is judged that there is an error thrown cancelTokenin promisethe chain call , and the request is canceled in the middle, so that the cancellation information is captured by the user. 5. Why do you support sending requests in the browser and also support sending requests? answer:dispatchRequestadapterrequest.abort()promiserejected
node
axios.defaults.adapterIn the default configuration, it is determined whether it is a browser or nodean environment according to the environment, and the corresponding adapter is used. Adapters support customization.

To answer the interviewer's questions, readers can also organize the language according to their own understanding. The author's answer is just for reference.

axios The source code is relatively small, more than a thousand lines after packaging, it is relatively easy to read, and it is very worth learning.

It is suggested  clone that Ruochuan's axios-analysis github warehouse should be debugged according to the method in the article, which will be more impressive.

Based on Promisethe composition of Promisethe chain, cleverly set up request interception, send the request, and then try to respond to the interceptor.

requestThe two modules of the interceptor in and dispatchthe cancellation in are relatively complex, and can be debugged and absorbed.

axios It is a function, and when it is a function, it calls a function, and it is an object. There are request methods such as , etc. Axios.prototype.requeston it , and finally a function is called.getpostAxios.prototype.request

axios A lot of design patterns are used in the source code. Such as factory pattern, iterator pattern, adapter pattern, etc. If you want to learn design patterns systematically, it is generally recommended to recommend JavaScript design patterns and development practices with a score of 9.1 on Douban

 

 

Guess you like

Origin blog.csdn.net/huihui_999/article/details/130994160