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
Domain: github.com
{"@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\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-controller | voltron_issues_fragments |
| route-action | issue_layout |
| fetch-nonce | v2:76612b89-1f90-1e62-5110-483732e810a9 |
| current-catalog-service-hash | 81bb79d38c15960b92d99bca9288a9108c7a47b18f2423d0f6438c5b7bcd2114 |
| request-id | 91AE:247E0C:A72020:E7F240:696A8630 |
| html-safe-nonce | 085b486d54fc1d7f0461c5ddaa4188fc20c71ee12e463e744b60d59d2d02f422 |
| visitor-payload | eyJyZWZlcnJlciI6IiIsInJlcXVlc3RfaWQiOiI5MUFFOjI0N0UwQzpBNzIwMjA6RTdGMjQwOjY5NkE4NjMwIiwidmlzaXRvcl9pZCI6IjE5ODMxMTExNjY4MzY3NzAzNTIiLCJyZWdpb25fZWRnZSI6ImlhZCIsInJlZ2lvbl9yZW5kZXIiOiJpYWQifQ== |
| visitor-hmac | 9e7be5b0916aa204491cd8f6a9d5db585bb032e04bbb3c96c2f36ba9a86b0d40 |
| hovercard-subject-tag | issue:694575361 |
| github-keyboard-shortcuts | repository,issues,copilot |
| google-site-verification | Apib7-x98H0j5cPqHWwSMm6dNU4GmODRoqxLiDzdx9I |
| octolytics-url | https://collector.github.com/github/collect |
| analytics-location | / |
| fb:app_id | 1401488693436528 |
| apple-itunes-app | app-id=1477376905, app-argument=https://github.com/_view_fragments/issues/show/sisterAn/JavaScript-Algorithms/104/issue_layout |
| twitter:image | https://opengraph.githubassets.com/9649576d82734964dd79570282f9afe395652654434e2ea0b36424e7c499843b/sisterAn/JavaScript-Algorithms/issues/104 |
| twitter:card | summary_large_image |
| og:image | https://opengraph.githubassets.com/9649576d82734964dd79570282f9afe395652654434e2ea0b36424e7c499843b/sisterAn/JavaScript-Algorithms/issues/104 |
| og:image:alt | axios 是目前最常用的 http 请求库,可以用于浏览器和 node.js 。 axios 的主要特性包括: 基于 Promise 支持浏览器和 node.js 可拦截请求与响应 可转换请求与响应数据 请求可以取消 自动转换 JSON 数据 客户端支持防范 XSRF 支持各主流浏览器及 IE8+ 这里所说的 支持浏览器和 node.js ,是指 axios 会自动判断当前所处的环境 如果... |
| og:image:width | 1200 |
| og:image:height | 600 |
| og:site_name | GitHub |
| og:type | object |
| og:author:username | sisterAn |
| hostname | github.com |
| expected-hostname | github.com |
| None | 913560fa317c3c5a71e34f9b19253c9f09d02b4b958a86c2a56f4c8541116377 |
| turbo-cache-control | no-preview |
| go-import | github.com/sisterAn/JavaScript-Algorithms git https://github.com/sisterAn/JavaScript-Algorithms.git |
| octolytics-dimension-user_id | 19721451 |
| octolytics-dimension-user_login | sisterAn |
| octolytics-dimension-repository_id | 252061924 |
| octolytics-dimension-repository_nwo | sisterAn/JavaScript-Algorithms |
| octolytics-dimension-repository_public | true |
| octolytics-dimension-repository_is_fork | false |
| octolytics-dimension-repository_network_root_id | 252061924 |
| octolytics-dimension-repository_network_root_nwo | sisterAn/JavaScript-Algorithms |
| turbo-body-classes | logged-out env-production page-responsive |
| disable-turbo | false |
| browser-stats-url | https://api.github.com/_private/browser/stats |
| browser-errors-url | https://api.github.com/_private/browser/errors |
| release | 5998c30593994bf2589055aef7b22d368a499367 |
| ui-target | full |
| theme-color | #1e2327 |
| color-scheme | light dark |
Links:
Viewport: width=device-width