René's URL Explorer Experiment


Title: 手写 axios 实现 · Issue #104 · sisterAn/JavaScript-Algorithms · GitHub

Open Graph Title: 手写 axios 实现 · Issue #104 · sisterAn/JavaScript-Algorithms

X Title: 手写 axios 实现 · Issue #104 · sisterAn/JavaScript-Algorithms

Description: axios 是目前最常用的 http 请求库,可以用于浏览器和 node.js 。 axios 的主要特性包括: 基于 Promise 支持浏览器和 node.js 可拦截请求与响应 可转换请求与响应数据 请求可以取消 自动转换 JSON 数据 客户端支持防范 XSRF 支持各主流浏览器及 IE8+ 这里所说的 支持浏览器和 node.js ,是指 axios 会自动判断当前所处的环境 如果是浏览器,就会基于 XMLHttpRequests 实现 axios 如果是 n...

Open Graph Description: axios 是目前最常用的 http 请求库,可以用于浏览器和 node.js 。 axios 的主要特性包括: 基于 Promise 支持浏览器和 node.js 可拦截请求与响应 可转换请求与响应数据 请求可以取消 自动转换 JSON 数据 客户端支持防范 XSRF 支持各主流浏览器及 IE8+ 这里所说的 支持浏览器和 node.js ,是指 axios 会自动判断当前所处的环境 如果...

X Description: axios 是目前最常用的 http 请求库,可以用于浏览器和 node.js 。 axios 的主要特性包括: 基于 Promise 支持浏览器和 node.js 可拦截请求与响应 可转换请求与响应数据 请求可以取消 自动转换 JSON 数据 客户端支持防范 XSRF 支持各主流浏览器及 IE8+ 这里所说的 支持浏览器和 node.js ,是指 axios 会自动判断当前所处的环境 如果...

Opengraph URL: https://github.com/sisterAn/JavaScript-Algorithms/issues/104

X: @github

direct link

Domain: github.com


Hey, it has json ld scripts:
{"@context":"https://schema.org","@type":"DiscussionForumPosting","headline":"手写 axios 实现","articleBody":"[axios](https://github.com/axios/axios) 是目前最常用的 http 请求库,可以用于浏览器和 node.js 。\r\n\r\naxios 的主要特性包括:\r\n\r\n- 基于 Promise\r\n- 支持浏览器和 node.js\r\n\r\n- 可拦截请求与响应\r\n- 可转换请求与响应数据\r\n\r\n- 请求可以取消\r\n- 自动转换 JSON 数据\r\n- 客户端支持防范 XSRF\r\n- 支持各主流浏览器及 IE8+\r\n\r\n\u003e  这里所说的 **支持浏览器和 node.js** ,是指 axios 会自动判断当前所处的环境\r\n\u003e\r\n\u003e - 如果是浏览器,就会基于 **XMLHttpRequests** 实现 axios\r\n\u003e - 如果是 node.js 环境,就会基于 node **内置核心模块http** 实现 axios\r\n\r\n### axios 使用\r\n\r\n#### 发送请求\r\n\r\n```js\r\naxios({\r\n  method:'get',\r\n  url:'http://bit.ly/2mTM3nY',\r\n  responseType:'stream'\r\n})\r\n  .then(function(response) {\r\n  response.data.pipe(fs.createWriteStream('ada_lovelace.jpg'))\r\n});\r\n```\r\n\r\n这是一个官方示例。从上面的代码中可以看到,axios 的用法与 jQuery 的 `ajax` 方法非常类似,两者都返回一个 `Promise` 对象(在这里也可以使用成功回调函数,但还是更推荐使用 `Promise` 或 `await`),然后再进行后续操作。\r\n\r\n#### 添加拦截器函数\r\n\r\n```js\r\n// 添加请求拦截器\r\naxios.interceptors.request.use(function (config) {\r\n    // Do something before request is sent\r\n    return config;\r\n  }, function (error) {\r\n    // Do something with request error\r\n    return Promise.reject(error);\r\n  });\r\n\r\n// 添加响应拦截器\r\naxios.interceptors.response.use(function (response) {\r\n    // Do something with response data\r\n    return response;\r\n  }, function (error) {\r\n    // Do something with response error\r\n    return Promise.reject(error);\r\n  });\r\n```\r\n\r\n从上面的代码,我们可以知道:发送请求之前,我们可以对请求的配置参数( `config` )做处理;在请求得到响应之后,我们可以对返回数据做处理。当请求或响应失败时,我们还能指定对应的错误处理函数。\r\n\r\n#### 撤销 HTTP 请求\r\n\r\n在开发与搜索相关的模块时,我们经常要频繁地发送数据查询请求。一般来说,当我们发送下一个请求时,需要撤销上个请求。因此,能撤销相关请求功能非常有用。`axios` 撤销请求的示例代码如下:\r\n\r\n```js\r\nconst CancelToken = axios.CancelToken;\r\nconst source = CancelToken.source();\r\n\r\naxios.get('/api/user', {\r\n  cancelToken: source.token\r\n}).catch(function(thrown) {\r\n  if (axios.isCancel(thrown)) {\r\n    console.log('请求撤销了', thrown.message);\r\n  } else {\r\n  }\r\n});\r\n\r\naxios.post('/api/user', {\r\n  name: 'pingzi'\r\n}, {\r\n  cancelToken: source.token\r\n}).\r\n\r\nsource.cancel('用户撤销了请求');\r\n```\r\n\r\n#### 内部流程图\r\n\r\n![](http://resource.muyiy.cn/image/20200920225609)\r\n\r\n### 源码分析\r\n\r\n| API                                                     | 类型             |\r\n| ------------------------------------------------------- | ---------------- |\r\n| axios(config)                                           | 发送请求         |\r\n| axios.create(config)                                    | 创建请求         |\r\n| axios.request(get post delete …)                        | 创建请求别名     |\r\n| axios.default                                           | 默认 config 配置 |\r\n| axios.interceptors                                      | 拦截器           |\r\n| axios.all() / axios.spread                              | 并行请求         |\r\n| axios.Cancel() / axios.CancelToken() / axios.isCancel() | 取消请求         |\r\n\r\n#### **1. 首先,先看看入口是怎么实现的:**\r\n\r\n```js\r\n\"use strict\";\r\n\r\nvar utils = require(\"./utils\");\r\nvar bind = require(\"./helpers/bind\");\r\nvar Axios = require(\"./core/Axios\");\r\nvar mergeConfig = require(\"./core/mergeConfig\");\r\nvar defaults = require(\"./defaults\");\r\n\r\n/**\r\n * 创建Axios实例\r\n */\r\nfunction createInstance(defaultConfig) {\r\n  // new Axios 得到一个上下文环境 包含defatults配置以及拦截器\r\n  var context = new Axios(defaultConfig);\r\n\r\n  // instance实例为bind返回的一个函数(即是request发送请求方法),此时this绑定到context上下文环境\r\n  var instance = bind(Axios.prototype.request, context);\r\n  // 将Axios构造函数中的原型方法绑定到instance上并且指定this作用域为context上下文环境\r\n  utils.extend(instance, Axios.prototype, context);\r\n  // 把上下文环境中的defaults 以及拦截器绑定到instance实例中\r\n  utils.extend(instance, context);\r\n\r\n  return instance;\r\n}\r\n\r\n// axios入口其实就是一个创建好的实例\r\nvar axios = createInstance(defaults);\r\n// 这句没太理解,根据作者的注释是:暴露Axios类去让类去继承\r\naxios.Axios = Axios;\r\n\r\n// 工厂函数 根据配置创建新的实例\r\naxios.create = function create(instanceConfig) {\r\n  return createInstance(mergeConfig(axios.defaults, instanceConfig));\r\n};\r\n\r\n// 绑定取消请求相关方法到入口对象\r\naxios.Cancel = require(\"./cancel/Cancel\");\r\naxios.CancelToken = require(\"./cancel/CancelToken\");\r\naxios.isCancel = require(\"./cancel/isCancel\");\r\n\r\n// all 和 spread 两个处理并行的静态方法\r\naxios.all = function all(promises) {\r\n  return Promise.all(promises);\r\n};\r\naxios.spread = require(\"./helpers/spread\");\r\n\r\nmodule.exports = axios;\r\n\r\n// 允许使用Ts 中的 default import 语法\r\nmodule.exports.default = axios;\r\n```\r\n\r\n `axios` 入口其实就是通过 `createInstance` 创建出的实例和 `axios.create()` 创建出的实例一样。而源码入口中的重中之中就是 `createInstance` 这个方法。`createInstance` 流程大致为:\r\n\r\n1. 使用 `Axios` 函数创建上下文 `context` ,包含自己的 `defaults` `config` 和 管理拦截器的数组\r\n2. 利用 `Axios.prototype.request` 和 上下文创建实例 `instance`,实例为一个 `request` 发送请求的函数 `this` 指向上下文 `context`\r\n3. 绑定 `Axios.prototype` 的其他方法到 `instance` 实例,this 指向上下文 `context`\r\n4. 把上下文 `context` 中的 `defaults` 和拦截器绑定到 `instance` 实例\r\n\r\n#### **2. 请求别名**\r\n\r\n在 `axios` 中 `axios.get` 、`axios.delete` 、`axios.head` 等别名请求方法其实都是指向同一个方法 `axios.request` 只是把 `default config` 中的 请求 `methods` 进行了修改而已。 具体代码在 `Axios` 这个构造函数的原型上,让我们来看下源码的实现:\r\n\r\n```js\r\nutils.forEach(\r\n  [\"delete\", \"get\", \"head\", \"options\"],\r\n  function forEachMethodNoData(method) {\r\n    Axios.prototype[method] = function(url, config) {\r\n      return this.request(\r\n        utils.merge(config || {}, {\r\n          method: method,\r\n          url: url\r\n        })\r\n      );\r\n    };\r\n  }\r\n);\r\n\r\nutils.forEach([\"post\", \"put\", \"patch\"], function forEachMethodWithData(method) {\r\n  Axios.prototype[method] = function(url, data, config) {\r\n    return this.request(\r\n      utils.merge(config || {}, {\r\n        method: method,\r\n        url: url,\r\n        data: data\r\n      })\r\n    );\r\n  };\r\n});\r\n```\r\n\r\n因为 `post` 、 `put` 、 `patch` 有请求体,所以要分开进行处理。请求别名方便用户快速使用各种不同 API 进行请求。\r\n\r\n#### **3. 拦截器的实现**\r\n\r\n首先在我们创建实例中,会去创建上下文实例 也就是 `new Axios` ,会得到 `interceptors` 这个属性,这个属性分别又有 `request` 和 `response` 两个属性 , 它们的值分别是 `new InterceptorManager` 构造函数返回的数组。这个构造函数同样负责拦截器数组的添加和移除。让我们看下源码:\r\n\r\n```js\r\n\"use strict\";\r\n\r\nvar utils = require(\"./../utils\");\r\n\r\nfunction InterceptorManager() {\r\n  this.handlers = [];\r\n}\r\n\r\n// axio或实例上调用 interceptors.request.use 或者 interceptors.resopnse.use\r\n// 传入的resolve, reject 将被添加入数组尾部\r\nInterceptorManager.prototype.use = function use(fulfilled, rejected) {\r\n  this.handlers.push({\r\n    fulfilled: fulfilled,\r\n    rejected: rejected\r\n  });\r\n  return this.handlers.length - 1;\r\n};\r\n// 移除拦截器,将该项在数组中置成null\r\nInterceptorManager.prototype.eject = function eject(id) {\r\n  if (this.handlers[id]) {\r\n    this.handlers[id] = null;\r\n  }\r\n};\r\n\r\n// 辅助方法,帮助便利拦截器数组,跳过被eject置成null的项\r\nInterceptorManager.prototype.forEach = function forEach(fn) {\r\n  utils.forEach(this.handlers, function forEachHandler(h) {\r\n    if (h !== null) {\r\n      fn(h);\r\n    }\r\n  });\r\n};\r\n\r\nmodule.exports = InterceptorManager;\r\n```\r\n\r\n上下文环境有了拦截器的数组, 又如何去 做到多个拦截器请求到响应的顺序处理以及实现呢?为了了解这点我们还需要进一步往下看 `Axios.protoType.request` 方法。\r\n\r\n#### **5. Axios.protoType.request**\r\n\r\n`Axios.protoType.request` 方法是请求开始的入口,分别处理了请求的 `config` ,以及链式处理请求拦截器 、请求、响应拦截器,并返回 `Proimse` 的格式方便我们处理回调。让我们来看下源码部分:\r\n\r\n```js\r\nAxios.prototype.request = function request(config) {\r\n  //判断参数类型,支持axios('url',{})以及axios(config)两种形式\r\n  if (typeof config === \"string\") {\r\n    config = arguments[1] || {};\r\n    config.url = arguments[0];\r\n  } else {\r\n    config = config || {};\r\n  }\r\n  //传入参数与axios或实例下的defaults属性合并\r\n  config = mergeConfig(this.defaults, config);\r\n  config.method = config.method ? config.method.toLowerCase() : \"get\";\r\n\r\n  // 创造一个请求序列数组,第一位是发送请求的方法,第二位是空\r\n  var chain = [dispatchRequest, undefined];\r\n  var promise = Promise.resolve(config);\r\n  //把实例中的拦请求截器数组依从加入头部\r\n  this.interceptors.request.forEach(function unshiftRequestInterceptors(\r\n    interceptor\r\n  ) {\r\n    chain.unshift(interceptor.fulfilled, interceptor.rejected);\r\n  });\r\n  //把实例中的拦截器数组依从加入尾部\r\n  this.interceptors.response.forEach(function pushResponseInterceptors(\r\n    interceptor\r\n  ) {\r\n    chain.push(interceptor.fulfilled, interceptor.rejected);\r\n  });\r\n  //遍历请求序列数组形成prmise链依次处理并且处理完弹出请求序列数组\r\n  while (chain.length) {\r\n    promise = promise.then(chain.shift(), chain.shift());\r\n  }\r\n  //返回最终promise对象\r\n  return promise;\r\n};\r\n```\r\n\r\n我们可以看到 `Axios.protoType.request` 中使用了精妙的封装方法,形成 `promise` 链 去依次挂载在 `then` 方法顺序处理。\r\n\r\n#### **6. 取消请求**\r\n\r\n`Axios.prototype.request` 调用 `dispatchRequest` 是最终处理 `axios` 发起请求的函数,执行过程流程包括了:\r\n\r\n1. 取消请求的处理和判断\r\n2. 处理 参数和默认参数\r\n3. 使用相对应的环境 `adapter` 发送请求(浏览器环境使用 `XMLRequest` 对象、`Node` 使用 `http` 对象)\r\n4. 返回后抛出取消请求 `message`,根据配置 `transformData` 转换 响应数据\r\n\r\n这一过程除了取消请求的处理, 其余的流程都相对十分的简单,所以我们要对取消请求进行详细的分析。我们还是先看调用方式:\r\n\r\n```js\r\nconst CancelToken = axios.CancelToken;\r\nconst source = CancelToken.source();\r\n\r\naxios\r\n  .get(\"/user/12345\", {\r\n    cancelToken: source.token\r\n  })\r\n  .catch(function(thrown) {\r\n    if (axios.isCancel(thrown)) {\r\n      console.log(\"Request canceled\", thrown.message);\r\n    } else {\r\n      // handle error\r\n    }\r\n  });\r\n\r\nsource.cancel(\"Operation canceled by the user.\");\r\n```\r\n\r\n从调用方式我们可以看到,我们需要从 `config` 传入 `axios.CancelToken.source().token` , 并且可以用 `axios.CancelToken.source().cancel()` 执行取消请求。我们还可以从 看出 `canel` 函数不仅是取消了请求,并且 使得整个请求走入了 `rejected` 。从整个 API 设计我们就可以看出这块的 功能可能有点复杂, 让我们一点点来分析,从 `CancelToken.source` 这个方法看实现过程 :\r\n\r\n```js\r\nCancelToken.source = function source() {\r\n  var cancel;\r\n  var token = new CancelToken(function executor(c) {\r\n    cancel = c;\r\n  });\r\n  return {\r\n    token: token,\r\n    cancel: cancel\r\n  };\r\n};\r\n```\r\n\r\n`axios.CancelToken.source().token` 返回的是一个 `new CancelToken` 的实例,`axios.CancelToken.source().cancel` , 是 `new CancelToken` 是传入 `new CancelToken` 中的方法的一个参数。再看下 `CancelToken` 这个构造函数:\r\n\r\n```js\r\nfunction CancelToken(executor) {\r\n  if (typeof executor !== \"function\") {\r\n    throw new TypeError(\"executor must be a function.\");\r\n  }\r\n\r\n  var resolvePromise;\r\n  this.promise = new Promise(function promiseExecutor(resolve) {\r\n    resolvePromise = resolve;\r\n  });\r\n\r\n  var token = this;\r\n  executor(function cancel(message) {\r\n    if (token.reason) {\r\n      return;\r\n    }\r\n\r\n    token.reason = new Cancel(message);\r\n    resolvePromise(token.reason);\r\n  });\r\n}\r\n```\r\n\r\n我们根据构造函数可以知道 `axios.CancelToken.source().token` 最终拿到的实例下挂载了 `promise` 和 `reason` 两个属性,`promise` 属性是一个处于 `pending` 状态的 `promise` 实例,`reason` 是执行 `cancel` 方法后传入的 `message` 。而 `axios.CancelToken.source().cancel` 是一个函数方法,负责判断是否执行,若未执行拿到 `axios.CancelToken.source().token.promise` 中 `executor` 的 `resolve` 参数,作为触发器,触发处于处于 `pending` 状态中的 `promise` 并且 传入的 `message` 挂载在 `axios.CancelToken.source().token.reason` 下。若有 已经挂载在 `reason` 下则返回防止反复触发。而这个 `pending` 状态的 `promise` 在 `cancel` 后又是怎么进入 `axios` 总体 `promise` 的 `rejected` 中呢。我们需要看看 `adpater` 中的处理:\r\n\r\n```js\r\n//如果有cancelToken\r\nif (config.cancelToken) {\r\n  config.cancelToken.promise.then(function onCanceled(cancel) {\r\n    if (!request) {\r\n      return;\r\n    }\r\n    //取消请求\r\n    request.abort();\r\n    //axios的promise进入rejected\r\n    reject(cancel);\r\n    // 清楚request请求对象\r\n    request = null;\r\n  });\r\n}\r\n```\r\n\r\n取消请求的总体逻辑大体如此,可能理解起来比较困难,需要反复看源码感受内部的流程,让我们大致在屡一下大致流程:\r\n\r\n1. `axios.CancelToken.source()` 返回一个对象,`tokens` 属性 `CancelToken` 类的实例,`cancel` 是 `tokens` 内部 `promise` 的 `reslove` 触发器\r\n2. `axios` 的 `config` 接受了 `CancelToken` 类的实例\r\n3. 当 `cancel` 触发处于 `pending` 中的 `tokens.promise` ,取消请求,把 `axios` 的 `promise` 走向 `rejected` 状态\r\n\r\n### 手写 axios\r\n\r\n看完了源码分析,下面手写一个 axios 就很容易了\r\n\r\n```js\r\n// util.js\r\n// 将一个对象(b)的方法或属性扩展到另外一个对象(a)上,并指定上下文(context)\r\nexport function extend(a, b, context) {\r\n  for(let key in b) {\r\n    if(b.hasOwnProperty(key)) {\r\n      if(typeof b[key] === 'function') {\r\n        a[key] = b[key].bind(context);\r\n      } else {\r\n        a[key] = b[key]\r\n      }\r\n    }\r\n  }\r\n}\r\n\r\n// 沈拷贝\r\nexport function deepClone(source) {\r\n  let target = Array.isArray(source) ? []: {}\r\n  for(let key in source) {\r\n    if(typeof source[key] === 'object' \u0026\u0026 source[key] !== null) {\r\n      target[key] = deepClone(source[key])\r\n    } else {\r\n      target[key] = source[key]\r\n    }\r\n  }\r\n  return target\r\n}\r\n\r\n// 合并 \r\nexport function mergeConfig(obj1, obj2) {\r\n  let target = deepClone(obj1),\r\n    source = deepClone(obj2)\r\n  return Object.keys(source).reduce((pre, cur) =\u003e {\r\n    if(['url', 'baseURL', 'method'].includes(cur)) {\r\n      pre[cur] = source[cur]\r\n    }\r\n    if(['headers', 'data', 'params'].includes(cur)) {\r\n      pre[cur] = Object.assign({}, source[cur])\r\n    }\r\n    return pre\r\n  }, target)\r\n}\r\n```\r\n\r\n```js\r\nimport {\r\n  extend,\r\n  deepClone,\r\n  mergeConfig\r\n} from './util'\r\n\r\n// 定义拦截器\r\nclass InterceptorsManager {\r\n  constructor() {\r\n    this.handlers = []\r\n  }\r\n  use(fulfilled, rejected) {\r\n    this.handlers.push({\r\n      fulfilled,\r\n      rejected\r\n    })\r\n    return this.handlers.length - 1\r\n  }\r\n  eject(id) {\r\n    if(this.handlers[id]) {\r\n      this.handlers[id] = null\r\n    }\r\n  }\r\n}\r\n\r\n// Axios\r\nclass Axios {\r\n  constructor(defaultConfig) {\r\n    this.defaults = deepClone(defaultConfig)\r\n    this.interceptors = {\r\n      request: new InterceptorsManager(),\r\n      response: new InterceptorsManager()\r\n    }\r\n  }\r\n  request(config) {\r\n    // 配置合并\r\n    let configs = mergeConfig(this.defaults, config)\r\n    // 初始请求序列数组,第一位是发送请求的方法,第二位是空\r\n    let chain = [this.sendAjax.bind(this), undefined]\r\n    // 请求拦截\r\n    this.interceptors.request.handlers.forEach(interceptor=\u003e{\r\n      chain.unshift(interceptor.fulfilled, interceptor.rejected)\r\n    })\r\n    // 响应拦截\r\n    this.interceptors.response.handlers.forEach(interceptor=\u003e{\r\n      chain.push(interceptor.fulfilled, interceptor.rejected)\r\n    })\r\n    // 执行队列,每次执行一对,并给 promise 赋最新的值\r\n    let promise = Promise.resolve(configs)\r\n    while(chain.length) {\r\n      // config 按序通过\r\n      // 不断将 config 从上一个 promise 传递到下一个 promise\r\n      promise = promise.then(chain.shift(), chain.shift())\r\n    }\r\n    return promise\r\n  }\r\n  sendAjax(config) {\r\n    return new Promise(resolve =\u003e {\r\n      const {\r\n        url = '', \r\n        method = 'get',\r\n        data = {}\r\n      } = config\r\n      // 发送 ajax 请求\r\n      const xhr = new XMLHttpRequest()\r\n      xhr.open(method, url, true)\r\n      xhr.onload = function() {\r\n        resolve(xhr.responseText)\r\n      }\r\n      xhr.send(data);\r\n    })\r\n  }\r\n}\r\n\r\n// 定义 get、post...方法,并挂载到 Axios 原型上\r\nconst methodArr = ['get', 'delete', 'head', 'options', 'put', 'patch', 'post']\r\nmethodArr.forEach(method=\u003e{\r\n  Axios.prototype[method] = function() {\r\n    // 无请求体\r\n    if(['get', 'delete', 'head', 'options'].includes(method)) {\r\n      return this.request({\r\n        method: method,\r\n        url: arguments[0],\r\n        ...arguments[1] || {}\r\n      })\r\n    } else {\r\n      // 有请求体\r\n      return this.request({\r\n        method: method,\r\n        url: arguments[0],\r\n        data: arguments[1] || {},\r\n        ...arguments[2] || {}\r\n      })\r\n    }\r\n  }\r\n}) \r\n\r\n// 最终导出 axios 的实例方法,即实例的 request 方法\r\nfunction createInstance(defaultConfig) {\r\n  // 创建一个 axios 实例\r\n  let context = new Axios(defaultConfig)\r\n  // 指定上下文\r\n  let instance = Axios.prototype.request.bind(context)\r\n  // 把 Axios.prototype 的方法扩展到 instance 对象上\r\n  // 这样 instance 就有了 get、post、put 等方法\r\n  // 并指定上下文为 context,这样执行 Axios 原型链上的方法时,this 就指向 context\r\n  extend(instance, Axios.prototype, context)\r\n  // 把context对象上的自身属性和方法扩展到instance上\r\n  // 注:因为extend内部使用的forEach方法对对象做for in 遍历时,只遍历对象本身的属性,而不会遍历原型链上的属性\r\n  // 这样,instance 就有了  defaults、interceptors 属性。(这两个属性后面我们会介绍)\r\n  extend(instance, context)\r\n  return instance\r\n}\r\n\r\n// 得到最后的全局变量 axios\r\nlet axios = createInstance(defaultConfig)\r\n\r\naxios.create = function create(instanceConfig) {\r\n  return createInstance(mergeConfig(axios.defaults, instanceConfig));\r\n}\r\n\r\nmodule.exports = axios;\r\n```\r\n\r\n### 常见面试题集锦\r\n\r\n\u003e 问:为什么 `axios` 既可以当函数调用,也可以当对象使用,比如`axios({})`、`axios.get`\r\n\u003e\r\n\u003e 答:`axios`本质是函数,赋值了一些别名方法,比如`get`、`post`方法,可被调用,最终调用的还是`Axios.prototype.request`函数。\r\n\r\n\u003e 问:简述 `axios` 调用流程\r\n\u003e\r\n\u003e 答:实际是调用的`Axios.prototype.request`方法,最终返回的是`promise`链式调用,实际请求是在`dispatchRequest`中派发的\r\n\r\n\u003e 问:有用过拦截器吗?原理是怎样的\r\n\u003e\r\n\u003e 答:用过,用`axios.interceptors.request.use`添加请求成功和失败拦截器函数,用`axios.interceptors.response.use`添加响应成功和失败拦截器函数。在`Axios.prototype.request`函数组成`promise`链式调用时,`Interceptors.protype.forEach`遍历请求和响应拦截器添加到真正发送请求`dispatchRequest`的两端,从而做到请求前拦截和响应后拦截。拦截器也支持用`Interceptors.protype.eject`方法移除\r\n\r\n\u003e 问:有使用`axios`的取消功能吗?是怎么实现的\r\n\u003e\r\n\u003e 答:用过,通过传递`config`配置`cancelToken`的形式,来取消的。判断有传`cancelToken`,在`promise`链式调用的`dispatchRequest`抛出错误,在`adapter`中`request.abort()`取消请求,使`promise`走向`rejected`,被用户捕获取消信息\r\n\r\n\u003e 问:为什么支持浏览器中发送请求也支持`node`发送请求\r\n\u003e\r\n\u003e 答:`axios.defaults.adapter`默认配置中根据环境判断是浏览器还是`node`环境,使用对应的适配器。适配器支持自定义\r\n\r\n\r\n\r\n### 参考链接\r\n\r\n[深入浅出 axios 源码](https://zhuanlan.zhihu.com/p/37962469)\r\n\r\n[如何写一个像 axios 那样优秀的请求库](https://mp.weixin.qq.com/s/d3Or96bd5LesUl9OR8Ca4g)\r\n\r\n[axios —— 极简封装的艺术](https://zhuanlan.zhihu.com/p/28396592)\r\n\r\n[学习 axios 源码整体架构,打造属于自己的请求库](https://zhuanlan.zhihu.com/p/97813399)\r\n\r\n[手写axios核心原理,再也不怕面试官问我axios原理](https://juejin.im/post/6856706569263677447)\r\n\r\n\r\n","author":{"url":"https://github.com/sisterAn","@type":"Person","name":"sisterAn"},"datePublished":"2020-09-06T23:48:14.000Z","interactionStatistic":{"@type":"InteractionCounter","interactionType":"https://schema.org/CommentAction","userInteractionCount":1},"url":"https://github.com/104/JavaScript-Algorithms/issues/104"}

route-pattern/_view_fragments/issues/show/:user_id/:repository/:id/issue_layout(.:format)
route-controllervoltron_issues_fragments
route-actionissue_layout
fetch-noncev2:76612b89-1f90-1e62-5110-483732e810a9
current-catalog-service-hash81bb79d38c15960b92d99bca9288a9108c7a47b18f2423d0f6438c5b7bcd2114
request-id91AE:247E0C:A72020:E7F240:696A8630
html-safe-nonce085b486d54fc1d7f0461c5ddaa4188fc20c71ee12e463e744b60d59d2d02f422
visitor-payloadeyJyZWZlcnJlciI6IiIsInJlcXVlc3RfaWQiOiI5MUFFOjI0N0UwQzpBNzIwMjA6RTdGMjQwOjY5NkE4NjMwIiwidmlzaXRvcl9pZCI6IjE5ODMxMTExNjY4MzY3NzAzNTIiLCJyZWdpb25fZWRnZSI6ImlhZCIsInJlZ2lvbl9yZW5kZXIiOiJpYWQifQ==
visitor-hmac9e7be5b0916aa204491cd8f6a9d5db585bb032e04bbb3c96c2f36ba9a86b0d40
hovercard-subject-tagissue:694575361
github-keyboard-shortcutsrepository,issues,copilot
google-site-verificationApib7-x98H0j5cPqHWwSMm6dNU4GmODRoqxLiDzdx9I
octolytics-urlhttps://collector.github.com/github/collect
analytics-location///voltron/issues_fragments/issue_layout
fb:app_id1401488693436528
apple-itunes-appapp-id=1477376905, app-argument=https://github.com/_view_fragments/issues/show/sisterAn/JavaScript-Algorithms/104/issue_layout
twitter:imagehttps://opengraph.githubassets.com/9649576d82734964dd79570282f9afe395652654434e2ea0b36424e7c499843b/sisterAn/JavaScript-Algorithms/issues/104
twitter:cardsummary_large_image
og:imagehttps://opengraph.githubassets.com/9649576d82734964dd79570282f9afe395652654434e2ea0b36424e7c499843b/sisterAn/JavaScript-Algorithms/issues/104
og:image:altaxios 是目前最常用的 http 请求库,可以用于浏览器和 node.js 。 axios 的主要特性包括: 基于 Promise 支持浏览器和 node.js 可拦截请求与响应 可转换请求与响应数据 请求可以取消 自动转换 JSON 数据 客户端支持防范 XSRF 支持各主流浏览器及 IE8+ 这里所说的 支持浏览器和 node.js ,是指 axios 会自动判断当前所处的环境 如果...
og:image:width1200
og:image:height600
og:site_nameGitHub
og:typeobject
og:author:usernamesisterAn
hostnamegithub.com
expected-hostnamegithub.com
None913560fa317c3c5a71e34f9b19253c9f09d02b4b958a86c2a56f4c8541116377
turbo-cache-controlno-preview
go-importgithub.com/sisterAn/JavaScript-Algorithms git https://github.com/sisterAn/JavaScript-Algorithms.git
octolytics-dimension-user_id19721451
octolytics-dimension-user_loginsisterAn
octolytics-dimension-repository_id252061924
octolytics-dimension-repository_nwosisterAn/JavaScript-Algorithms
octolytics-dimension-repository_publictrue
octolytics-dimension-repository_is_forkfalse
octolytics-dimension-repository_network_root_id252061924
octolytics-dimension-repository_network_root_nwosisterAn/JavaScript-Algorithms
turbo-body-classeslogged-out env-production page-responsive
disable-turbofalse
browser-stats-urlhttps://api.github.com/_private/browser/stats
browser-errors-urlhttps://api.github.com/_private/browser/errors
release5998c30593994bf2589055aef7b22d368a499367
ui-targetfull
theme-color#1e2327
color-schemelight dark

Links:

Skip to contenthttps://github.com/sisterAn/JavaScript-Algorithms/issues/104#start-of-content
https://github.com/
Sign in https://github.com/login?return_to=https%3A%2F%2Fgithub.com%2FsisterAn%2FJavaScript-Algorithms%2Fissues%2F104
GitHub CopilotWrite better code with AIhttps://github.com/features/copilot
GitHub SparkBuild and deploy intelligent appshttps://github.com/features/spark
GitHub ModelsManage and compare promptshttps://github.com/features/models
MCP RegistryNewIntegrate external toolshttps://github.com/mcp
ActionsAutomate any workflowhttps://github.com/features/actions
CodespacesInstant dev environmentshttps://github.com/features/codespaces
IssuesPlan and track workhttps://github.com/features/issues
Code ReviewManage code changeshttps://github.com/features/code-review
GitHub Advanced SecurityFind and fix vulnerabilitieshttps://github.com/security/advanced-security
Code securitySecure your code as you buildhttps://github.com/security/advanced-security/code-security
Secret protectionStop leaks before they starthttps://github.com/security/advanced-security/secret-protection
Why GitHubhttps://github.com/why-github
Documentationhttps://docs.github.com
Bloghttps://github.blog
Changeloghttps://github.blog/changelog
Marketplacehttps://github.com/marketplace
View all featureshttps://github.com/features
Enterpriseshttps://github.com/enterprise
Small and medium teamshttps://github.com/team
Startupshttps://github.com/enterprise/startups
Nonprofitshttps://github.com/solutions/industry/nonprofits
App Modernizationhttps://github.com/solutions/use-case/app-modernization
DevSecOpshttps://github.com/solutions/use-case/devsecops
DevOpshttps://github.com/solutions/use-case/devops
CI/CDhttps://github.com/solutions/use-case/ci-cd
View all use caseshttps://github.com/solutions/use-case
Healthcarehttps://github.com/solutions/industry/healthcare
Financial serviceshttps://github.com/solutions/industry/financial-services
Manufacturinghttps://github.com/solutions/industry/manufacturing
Governmenthttps://github.com/solutions/industry/government
View all industrieshttps://github.com/solutions/industry
View all solutionshttps://github.com/solutions
AIhttps://github.com/resources/articles?topic=ai
Software Developmenthttps://github.com/resources/articles?topic=software-development
DevOpshttps://github.com/resources/articles?topic=devops
Securityhttps://github.com/resources/articles?topic=security
View all topicshttps://github.com/resources/articles
Customer storieshttps://github.com/customer-stories
Events & webinarshttps://github.com/resources/events
Ebooks & reportshttps://github.com/resources/whitepapers
Business insightshttps://github.com/solutions/executive-insights
GitHub Skillshttps://skills.github.com
Documentationhttps://docs.github.com
Customer supporthttps://support.github.com
Community forumhttps://github.com/orgs/community/discussions
Trust centerhttps://github.com/trust-center
Partnershttps://github.com/partners
GitHub SponsorsFund open source developershttps://github.com/sponsors
Security Labhttps://securitylab.github.com
Maintainer Communityhttps://maintainers.github.com
Acceleratorhttps://github.com/accelerator
Archive Programhttps://archiveprogram.github.com
Topicshttps://github.com/topics
Trendinghttps://github.com/trending
Collectionshttps://github.com/collections
Enterprise platformAI-powered developer platformhttps://github.com/enterprise
GitHub Advanced SecurityEnterprise-grade security featureshttps://github.com/security/advanced-security
Copilot for BusinessEnterprise-grade AI featureshttps://github.com/features/copilot/copilot-business
Premium SupportEnterprise-grade 24/7 supporthttps://github.com/premium-support
Pricinghttps://github.com/pricing
Search syntax tipshttps://docs.github.com/search-github/github-code-search/understanding-github-code-search-syntax
documentationhttps://docs.github.com/search-github/github-code-search/understanding-github-code-search-syntax
Sign in https://github.com/login?return_to=https%3A%2F%2Fgithub.com%2FsisterAn%2FJavaScript-Algorithms%2Fissues%2F104
Sign up https://github.com/signup?ref_cta=Sign+up&ref_loc=header+logged+out&ref_page=%2F%3Cuser-name%3E%2F%3Crepo-name%3E%2Fvoltron%2Fissues_fragments%2Fissue_layout&source=header-repo&source_repo=sisterAn%2FJavaScript-Algorithms
Reloadhttps://github.com/sisterAn/JavaScript-Algorithms/issues/104
Reloadhttps://github.com/sisterAn/JavaScript-Algorithms/issues/104
Reloadhttps://github.com/sisterAn/JavaScript-Algorithms/issues/104
sisterAn https://github.com/sisterAn
JavaScript-Algorithmshttps://github.com/sisterAn/JavaScript-Algorithms
Notifications https://github.com/login?return_to=%2FsisterAn%2FJavaScript-Algorithms
Fork 649 https://github.com/login?return_to=%2FsisterAn%2FJavaScript-Algorithms
Star 5.7k https://github.com/login?return_to=%2FsisterAn%2FJavaScript-Algorithms
Code https://github.com/sisterAn/JavaScript-Algorithms
Issues 158 https://github.com/sisterAn/JavaScript-Algorithms/issues
Pull requests 0 https://github.com/sisterAn/JavaScript-Algorithms/pulls
Actions https://github.com/sisterAn/JavaScript-Algorithms/actions
Projects 0 https://github.com/sisterAn/JavaScript-Algorithms/projects
Security Uh oh! There was an error while loading. Please reload this page. https://github.com/sisterAn/JavaScript-Algorithms/security
Please reload this pagehttps://github.com/sisterAn/JavaScript-Algorithms/issues/104
Insights https://github.com/sisterAn/JavaScript-Algorithms/pulse
Code https://github.com/sisterAn/JavaScript-Algorithms
Issues https://github.com/sisterAn/JavaScript-Algorithms/issues
Pull requests https://github.com/sisterAn/JavaScript-Algorithms/pulls
Actions https://github.com/sisterAn/JavaScript-Algorithms/actions
Projects https://github.com/sisterAn/JavaScript-Algorithms/projects
Security https://github.com/sisterAn/JavaScript-Algorithms/security
Insights https://github.com/sisterAn/JavaScript-Algorithms/pulse
New issuehttps://github.com/login?return_to=https://github.com/sisterAn/JavaScript-Algorithms/issues/104
New issuehttps://github.com/login?return_to=https://github.com/sisterAn/JavaScript-Algorithms/issues/104
手写 axios 实现https://github.com/sisterAn/JavaScript-Algorithms/issues/104#top
手写源码https://github.com/sisterAn/JavaScript-Algorithms/issues?q=state%3Aopen%20label%3A%22%E6%89%8B%E5%86%99%E6%BA%90%E7%A0%81%22
https://github.com/sisterAn
https://github.com/sisterAn
sisterAnhttps://github.com/sisterAn
on Sep 6, 2020https://github.com/sisterAn/JavaScript-Algorithms/issues/104#issue-694575361
axioshttps://github.com/axios/axios
https://camo.githubusercontent.com/086896016341b57f1a854bc2840c58a0778d4b8aaa3b366c0dc9dd749c50f5a4/687474703a2f2f7265736f757263652e6d757969792e636e2f696d6167652f3230323030393230323235363039
深入浅出 axios 源码https://zhuanlan.zhihu.com/p/37962469
如何写一个像 axios 那样优秀的请求库https://mp.weixin.qq.com/s/d3Or96bd5LesUl9OR8Ca4g
axios —— 极简封装的艺术https://zhuanlan.zhihu.com/p/28396592
学习 axios 源码整体架构,打造属于自己的请求库https://zhuanlan.zhihu.com/p/97813399
手写axios核心原理,再也不怕面试官问我axios原理https://juejin.im/post/6856706569263677447
手写源码https://github.com/sisterAn/JavaScript-Algorithms/issues?q=state%3Aopen%20label%3A%22%E6%89%8B%E5%86%99%E6%BA%90%E7%A0%81%22
https://github.com
Termshttps://docs.github.com/site-policy/github-terms/github-terms-of-service
Privacyhttps://docs.github.com/site-policy/privacy-policies/github-privacy-statement
Securityhttps://github.com/security
Statushttps://www.githubstatus.com/
Communityhttps://github.community/
Docshttps://docs.github.com/
Contacthttps://support.github.com?tags=dotcom-footer

Viewport: width=device-width


URLs of crawlers that visited me.