{"id":28788894,"url":"https://github.com/ronffy/axios-tutorial","last_synced_at":"2025-10-27T20:49:45.606Z","repository":{"id":93092626,"uuid":"133899996","full_name":"ronffy/axios-tutorial","owner":"ronffy","description":"axios实例应用及源码剖析 - xhr篇 （走心教程）","archived":false,"fork":false,"pushed_at":"2019-03-04T11:46:26.000Z","size":327,"stargazers_count":251,"open_issues_count":0,"forks_count":37,"subscribers_count":6,"default_branch":"master","last_synced_at":"2025-07-04T22:50:23.179Z","etag":null,"topics":["ajax","axios","axios-promise-xhr","http","xhr","xmlhttprequest"],"latest_commit_sha":null,"homepage":"","language":"JavaScript","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":"mit","status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/ronffy.png","metadata":{"files":{"readme":"README.md","changelog":null,"contributing":null,"funding":null,"license":"LICENSE","code_of_conduct":null,"threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":null,"support":null,"governance":null,"roadmap":null,"authors":null,"dei":null,"publiccode":null,"codemeta":null,"zenodo":null}},"created_at":"2018-05-18T03:49:08.000Z","updated_at":"2025-04-24T02:27:40.000Z","dependencies_parsed_at":"2023-06-18T15:59:23.004Z","dependency_job_id":null,"html_url":"https://github.com/ronffy/axios-tutorial","commit_stats":null,"previous_names":[],"tags_count":0,"template":false,"template_full_name":null,"purl":"pkg:github/ronffy/axios-tutorial","repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/ronffy%2Faxios-tutorial","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/ronffy%2Faxios-tutorial/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/ronffy%2Faxios-tutorial/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/ronffy%2Faxios-tutorial/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/ronffy","download_url":"https://codeload.github.com/ronffy/axios-tutorial/tar.gz/refs/heads/master","sbom_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/ronffy%2Faxios-tutorial/sbom","scorecard":null,"host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":281340696,"owners_count":26484446,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2022-07-04T15:15:14.044Z","status":"online","status_checked_at":"2025-10-27T02:00:05.855Z","response_time":61,"last_error":null,"robots_txt_status":"success","robots_txt_updated_at":"2025-07-24T06:49:26.215Z","robots_txt_url":"https://github.com/robots.txt","online":true,"can_crawl_api":true,"host_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub","repositories_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories","repository_names_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repository_names","owners_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners"}},"keywords":["ajax","axios","axios-promise-xhr","http","xhr","xmlhttprequest"],"created_at":"2025-06-17T21:04:32.416Z","updated_at":"2025-10-27T20:49:45.595Z","avatar_url":"https://github.com/ronffy.png","language":"JavaScript","readme":"# Axios源码深度剖析 - XHR篇\n\n[axios](https://github.com/axios/axios) 是一个基于 Promise 的http请求库，可以用在浏览器和node.js中，目前在github上有 42K 的star数\n\n## 分析axios - 目录\n\n-   [axios项目目录结构](#axios项目目录结构)\n-   [名词解释](#名词解释)\n-   [axios内部的运作流程图](#axios内部的运作流程图)\n-   [工具方法简单介绍](#工具方法简单介绍)\n-   [axios为何会有多种使用方式](#axios为何会有多种使用方式)\n-   [用户配置的config是怎么起作用的](#用户配置的config是怎么起作用的)\n-   [axios.prototype.request](#axios.prototype.request)\n-   [如何拦截请求响应并修改请求参数修改响应数据](#如何拦截请求响应并修改请求参数修改响应数据)\n-   [dispatchrequest都做了哪些事](#dispatchrequest都做了哪些事)\n-   [axios是如何用promise搭起基于xhr的异步桥梁的](#axios是如何用promise搭起基于xhr的异步桥梁的)\n-   [数据转换器-转换请求与响应数据](#数据转换器-转换请求与响应数据)\n-   [自动转换json数据](#自动转换json数据)\n-   [header设置](#header设置)\n-   [如何取消已经发送的请求](#如何取消已经发送的请求)\n-   [跨域携带cookie](#跨域携带cookie)\n-   [超时配置及处理](#超时配置及处理)\n-   [改写验证成功或失败的规则validatestatus](#改写验证成功或失败的规则validatestatus)\n-   [总结](#总结)\n\n### 备注：\n\n1. 每一小节都会从两个方面介绍：如何使用 -\u003e 源码分析\n2. [工具方法简单介绍]一节可先跳过，后面用到了再过来查看\n3. axios最核心的技术点是[如何拦截请求响应并修改请求参数修改响应数据](#[如何拦截请求响应并修改请求参数修改响应数据]) 和 [axios是如何用promise搭起基于xhr的异步桥梁的](#axios是如何用promise搭起基于xhr的异步桥梁的) \n\n\n## axios的应用和源码解析\n\n### axios项目目录结构\n\n```\n\n├── /dist/                     # 项目输出目录\n├── /lib/                      # 项目源码目录\n│ ├── /cancel/                 # 定义取消功能\n│ ├── /core/                   # 一些核心功能\n│ │ ├── Axios.js               # axios的核心主类\n│ │ ├── dispatchRequest.js     # 用来调用http请求适配器方法发送请求\n│ │ ├── InterceptorManager.js  # 拦截器构造函数\n│ │ └── settle.js              # 根据http响应状态，改变Promise的状态\n│ ├── /helpers/                # 一些辅助方法\n│ ├── /adapters/               # 定义请求的适配器 xhr、http\n│ │ ├── http.js                # 实现http适配器\n│ │ └── xhr.js                 # 实现xhr适配器\n│ ├── axios.js                 # 对外暴露接口\n│ ├── defaults.js              # 默认配置 \n│ └── utils.js                 # 公用工具\n├── package.json               # 项目信息\n├── index.d.ts                 # 配置TypeScript的声明文件\n└── index.js                   # 入口文件\n\n```\n\n注：因为我们需要要看的代码都是`/lib/`目录下的文件，所以以下所有涉及到文件路径的地方，\n我们都会在`/lib/`下进行查找\n\n\n### 名词解释\n\n-   拦截器 interceptors \n\n    （如果你熟悉中间件，那么就很好理解了，因为它起到的就是基于promise的中间件的作用）\n\n    拦截器分为请求拦截器和响应拦截器，顾名思义：\n    请求拦截器(`interceptors.request`)是指可以拦截住每次或指定http请求，并可修改配置项\n    响应拦截器(`interceptors.response`)可以在每次http请求后拦截住每次或指定http请求，并可修改返回结果项。\n\n    这里先简单说明，后面会做详细的介绍[如何拦截请求响应并修改请求参数修改响应数据](#如何拦截请求响应并修改请求参数修改响应数据)。\n\n-   数据转换器 （其实就是对数据进行转换，比如将对象转换为JSON字符串）\n\n    数据转换器分为请求转换器和响应转换器，顾名思义：\n    请求转换器(`transformRequest`)是指在请求前对数据进行转换，\n    响应转换器(`transformResponse`)主要对请求响应后的响应体做数据转换。\n\n-   http请求适配器（其实就是一个方法）\n\n    在axios项目里，http请求适配器主要指两种：XHR、http。\n    XHR的核心是浏览器端的XMLHttpRequest对象，\n    http核心是node的http[s].request方法\n\n    当然，axios也留给了用户通过config自行配置适配器的接口的，\n    不过，一般情况下，这两种适配器就能够满足从浏览器端向服务端发请求或者从node的http客户端向服务端发请求的需求。\n\n    本次分享主要围绕XHR。\n\n-   config配置项 （其实就是一个对象）\n\n    此处我们说的config，在项目内不是真的都叫config这个变量名，这个名字是我根据它的用途起的一个名字，方便大家理解。\n\n    在axios项目中的，设置\\读取config时，\n    有的地方叫它`defaults`(`/lib/defaults.js`)，这儿是默认配置项，\n    有的地方叫它`config`，如`Axios.prototype.request`的参数，再如`xhrAdapter`http请求适配器方法的参数。\n\n    config在axios项目里的是非常重要的一条链，是用户跟axios项目内部“通信”的主要桥梁。\n\n\n### axios内部的运作流程图\n\n![](axios-tree.jpg)\n\n\n### 工具方法简单介绍\n\n（注：本节可先跳过，后面用到了再过来查看）\n\n有一些方法在项目中多处使用，简单介绍下这些方法\n\n1. bind： 给某个函数指定上下文，也就是this指向\n\n```javascript\n\nbind(fn, context); \n\n```\n\n实现效果同`Function.prototype.bind`方法: `fn.bind(context)`\n\n2. forEach：遍历数组或对象\n\n```javascript\n\nvar utils = require('./utils');\nvar forEach = utils.forEach;\n\n// 数组\nutils.forEach([], (value, index, array) =\u003e {})\n\n// 对象\nutils.forEach({}, (value, key, object) =\u003e {})\n\n```\n\n3. merge：深度合并多个对象为一个对象\n\n```javascript\n\nvar utils = require('./utils');\nvar merge = utils.merge;\n\nvar obj1 = {\n  a: 1,\n  b: {\n    bb: 11,\n    bbb: 111,\n  }\n};\nvar obj2 = {\n  a: 2,\n  b: {\n    bb: 22,\n  }\n};\nvar mergedObj = merge(obj1, obj2); \n\n```\n\nmergedObj对象是：\n\n```javascript\n\n{ \n  a: 2, \n  b: { \n    bb: 22, \n    bbb: 111 \n  } \n}\n\n```\n\n\n4. extend：将一个对象的方法和属性扩展到另外一个对象上，并指定上下文\n\n```javascript\n\nvar utils = require('./utils');\nvar extend = utils.extend;\n\nvar context = {\n  a: 4,\n};\nvar target = {\n  k: 'k1',\n  fn(){\n    console.log(this.a + 1)\n  }\n};\nvar source = {\n  k: 'k2',\n  fn(){\n    console.log(this.a - 1)\n  }\n};\nlet extendObj = extend(target, source, context);\n\n```\n\nextendObj对象是：\n\n```javascript\n\n{\n  k: 'k2',\n  fn: source.fn.bind(context),\n}\n\n```\n执行`extendObj.fn();`, 打印`3`\n\n\n### axios为何会有多种使用方式\n\n#### 如何使用\n\n```javascript\n\n// 首先将axios包引进来\nimport axios from 'axios'\n\n```\n\n第1种使用方式：`axios(option)`\n\n```javascript\n\naxios({\n  url,\n  method,\n  headers,\n})\n\n```\n\n第2种使用方式：`axios(url[, option])`\n\n```javascript\n\naxios(url, {\n  method,\n  headers,\n})\n\n```\n\n第3种使用方式（对于`get、delete`等方法）：`axios[method](url[, option])`\n\n```javascript\n\naxios.get(url, {\n  headers,\n})\n\n```\n\n第4种使用方式（对于`post、put`等方法）：`axios[method](url[, data[, option]])`\n\n```javascript\n\naxios.post(url, data, {\n  headers,\n})\n\n```\n\n第5种使用方式：`axios.request(option)`\n\n```javascript\n\naxios.request({\n  url,\n  method,\n  headers,\n})\n\n```\n\n#### 源码分析\n\n作为axios项目的入口文件，我们先来看下`axios.js`的源码\n能够实现axios的多种使用方式的核心是`createInstance`方法：\n\n```javascript\n\n// /lib/axios.js\nfunction createInstance(defaultConfig) {\n  // 创建一个Axios实例\n  var context = new Axios(defaultConfig);\n\n  // 以下代码也可以这样实现：var instance = Axios.prototype.request.bind(context);\n  // 这样instance就指向了request方法，且上下文指向context，所以可以直接以 instance(option) 方式调用 \n  // Axios.prototype.request 内对第一个参数的数据类型判断，使我们能够以 instance(url, option) 方式调用\n  var instance = bind(Axios.prototype.request, context);\n\n  // 把Axios.prototype上的方法扩展到instance对象上，\n  // 这样 instance 就有了 get、post、put等方法\n  // 并指定上下文为context，这样执行Axios原型链上的方法时，this会指向context\n  utils.extend(instance, Axios.prototype, context);\n\n  // 把context对象上的自身属性和方法扩展到instance上\n  // 注：因为extend内部使用的forEach方法对对象做for in 遍历时，只遍历对象本身的属性，而不会遍历原型链上的属性\n  // 这样，instance 就有了  defaults、interceptors 属性。（这两个属性后面我们会介绍）\n  utils.extend(instance, context);\n\n  return instance;\n}\n\n// 接收默认配置项作为参数（后面会介绍配置项），创建一个Axios实例，最终会被作为对象导出\nvar axios = createInstance(defaults);\n\n```\n\n以上代码看上去很绕，其实`createInstance`最终是希望拿到一个Function，这个Function指向`Axios.prototype.request`，这个Function还会有`Axios.prototype`上的每个方法作为静态方法，且这些方法的上下文都是指向同一个对象。\n\n那么再来看看`Axios、Axios.prototype.request`的源码是怎样的？\n\n`Axios`是axios包的核心，一个`Axios`实例就是一个axios应用，其他方法都是对`Axios`内容的扩展\n而`Axios`构造函数的核心方法是`request`方法，各种axios的调用方式最终都是通过`request`方法发请求的\n\n```javascript\n\n// /lib/core/Axios.js\nfunction Axios(instanceConfig) {\n  this.defaults = instanceConfig;\n  this.interceptors = {\n    request: new InterceptorManager(),\n    response: new InterceptorManager()\n  };\n}\n\nAxios.prototype.request = function request(config) {\n  // ...省略代码\n};\n\n// 为支持的请求方法提供别名\nutils.forEach(['delete', 'get', 'head', 'options'], function forEachMethodNoData(method) {\n  Axios.prototype[method] = function(url, config) {\n    return this.request(utils.merge(config || {}, {\n      method: method,\n      url: url\n    }));\n  };\n});\nutils.forEach(['post', 'put', 'patch'], function forEachMethodWithData(method) {\n  Axios.prototype[method] = function(url, data, config) {\n    return this.request(utils.merge(config || {}, {\n      method: method,\n      url: url,\n      data: data\n    }));\n  };\n});\n\n```\n\n通过以上代码，我们就可以以多种方式发起http请求了: `axios()、axios.get()、axios.post()`\n\n一般情况，项目使用默认导出的axios实例就可以满足需求了，\n如果不满足需求需要创建新的axios实例，axios包也预留了接口，\n看下面的代码：\n\n```javascript\n\n// /lib/axios.js  -  31行\naxios.Axios = Axios;\naxios.create = function create(instanceConfig) {\n  return createInstance(utils.merge(defaults, instanceConfig));\n};\n\n```\n\n说完axios为什么会有这么多种使用方式，可能你心中会有一个疑问：\n使用axios时，无论`get`方法还是`post`方法，最终都是调用的`Axios.prototype.request`方法，那么这个方法是怎么根据我们的config配置发请求的呢？\n\n在开始说`Axios.prototype.request`之前，我们先来捋一捋在axios项目中，用户配置的config是怎么起作用的？\n\n\n### 用户配置的config是怎么起作用的\n\n这里说的`config`，指的是贯穿整个项目的配置项对象，\n通过这个对象，可以设置：\n\n`http请求适配器、请求地址、请求方法、请求头header、\n请求数据、请求或响应数据的转换、请求进度、http状态码验证规则、超时、取消请求等`\n\n可以发现，几乎`axios`所有的功能都是通过这个对象进行配置和传递的，\n既是`axios`项目内部的沟通桥梁，也是用户跟`axios`进行沟通的桥梁。\n\n首先我们看看，用户能以什么方式定义配置项:\n\n```javascript\n\nimport axios from 'axios'\n\n// 第1种：直接修改Axios实例上defaults属性，主要用来设置通用配置\naxios.defaults[configName] = value;\n\n// 第2种：发起请求时最终会调用Axios.prototype.request方法，然后传入配置项，主要用来设置“个例”配置\naxios({\n  url,\n  method,\n  headers,\n})\n\n// 第3种：新建一个Axios实例，传入配置项，此处设置的是通用配置\nlet newAxiosInstance = axios.create({\n  [configName]: value,\n})\n\n```\n\n看下 `Axios.prototype.request` 方法里的一行代码: (`/lib/core/Axios.js`  -  第35行)\n\n```javascript\n\nconfig = utils.merge(defaults, {method: 'get'}, this.defaults, config);\n\n```\n\n可以发现此处将默认配置对象`defaults`（`/lib/defaults.js`）、Axios实例属性`this.defaults`、`request`请求的参数`config`进行了合并。\n\n由此得出，多处配置的优先级由低到高是：\n—\u003e 默认配置对象`defaults`（`/lib/defaults.js`)  \n—\u003e  { method: 'get' }  \n—\u003e  Axios实例属性`this.defaults`   \n—\u003e   `request`请求的参数`config`\n\n留给大家思考一个问题: `defaults` 和 `this.defaults` 什么时候配置是相同的，什么时候是不同的？\n\n至此，我们已经得到了将多处`merge`后的`config`对象，那么这个对象在项目中又是怎样传递的呢？\n\n```javascript\n\nAxios.prototype.request = function request(config) {\n  // ...\n  config = utils.merge(defaults, {method: 'get'}, this.defaults, config);\n\n  var chain = [dispatchRequest, undefined];\n  // 将config对象当作参数传给Primise.resolve方法\n  var promise = Promise.resolve(config);\n\n  // ...省略代码\n  \n  while (chain.length) {\n    // config会按序通过 请求拦截器 - dispatchRequest方法 - 响应拦截器\n    // 关于拦截器 和 dispatchRequest方法，下面会作为一个专门的小节来介绍。\n    promise = promise.then(chain.shift(), chain.shift());\n  }\n\n  return promise;\n};\n\n```\n\n至此，`config`走完了它传奇的一生 `-_-`\n下一节就要说到重头戏了: `Axios.prototype.request`\n\n\n### axios.prototype.request\n\n这里面的代码比较复杂，一些方法需要追根溯源才能搞清楚，\n所以只需对chain数组有个简单的了解就好，涉及到的[拦截器](#如何拦截请求响应并修改请求参数修改响应数据)、[`dispatchRequest`]后面都会详细介绍\n\n`chain`数组是用来盛放拦截器方法和`dispatchRequest`方法的，\n通过promise从`chain`数组里按序取出回调函数逐一执行，最后将处理后的新的promise在`Axios.prototype.request`方法里返回出去，\n并将response或error传送出去，这就是`Axios.prototype.request`的使命了。\n\n查看源码：\n\n```javascript\n\n// /lib/core/Axios.js\nAxios.prototype.request = function request(config) {\n  // ...\n  var chain = [dispatchRequest, undefined];\n  var promise = Promise.resolve(config);\n\n  this.interceptors.request.forEach(function unshiftRequestInterceptors(interceptor) {\n    chain.unshift(interceptor.fulfilled, interceptor.rejected);\n  });\n  this.interceptors.response.forEach(function pushResponseInterceptors(interceptor) {\n    chain.push(interceptor.fulfilled, interceptor.rejected);\n  });\n\n  while (chain.length) {\n    promise = promise.then(chain.shift(), chain.shift());\n  }\n\n  return promise;\n};\n\n```\n\n此时，你一定对拦截器充满了好奇，这个拦截器到底是个什么家伙，下一节就让我们一探究竟吧\n\n\n### 如何拦截请求响应并修改请求参数修改响应数据\n\n#### 如何使用\n\n```javascript\n\n// 添加请求拦截器\nconst myRequestInterceptor = axios.interceptors.request.use(config =\u003e {\n    // 在发送http请求之前做些什么\n    return config; // 有且必须有一个config对象被返回\n}, error =\u003e {\n    // 对请求错误做些什么\n    return Promise.reject(error);\n});\n\n// 添加响应拦截器\naxios.interceptors.response.use(response =\u003e {\n  // 对响应数据做点什么\n  return response; // 有且必须有一个response对象被返回\n}, error =\u003e {\n  // 对响应错误做点什么\n  return Promise.reject(error);\n});\n\n// 移除某次拦截器\naxios.interceptors.request.eject(myRequestInterceptor);\n\n```\n\n#### 思考\n\n1. 是否可以直接 return error？\n\n```javascript\n\naxios.interceptors.request.use(config =\u003e config, error =\u003e {\n  // 是否可以直接 return error ？\n  return Promise.reject(error); \n});\n\n```\n\n2. 如何实现promise的链式调用\n\n```javascript\n\nnew People('whr').sleep(3000).eat('apple').sleep(5000).eat('durian');\n\n// 打印结果\n// (等待3s)--\u003e 'whr eat apple' -(等待5s)--\u003e 'whr eat durian'\n\n```\n\n#### 源码分析\n\n关于拦截器，[名词解释](#名词解释)一节已经做过简单说明。\n\n每个axios实例都有一个`interceptors`实例属性，\n`interceptors`对象上有两个属性`request`、`response`。\n\n```javascript\n\nfunction Axios(instanceConfig) {\n  // ...\n  this.interceptors = {\n    request: new InterceptorManager(),\n    response: new InterceptorManager()\n  };\n}\n\n```\n\n这两个属性都是一个`InterceptorManager`实例，而这个`InterceptorManager`构造函数就是用来管理拦截器的。\n\n我们先来看看`InterceptorManager`构造函数：\n\n`InterceptorManager`构造函数就是用来实现拦截器的，这个构造函数原型上有3个方法：use、eject、forEach。\n关于源码，其实是比较简单的，都是用来操作该构造函数的handlers实例属性的。\n\n\n```javascript\n\n// /lib/core/InterceptorManager.js\n\nfunction InterceptorManager() {\n  this.handlers = []; // 存放拦截器方法，数组内每一项都是有两个属性的对象，两个属性分别对应成功和失败后执行的函数。\n}\n\n// 往拦截器里添加拦截方法\nInterceptorManager.prototype.use = function use(fulfilled, rejected) {\n  this.handlers.push({\n    fulfilled: fulfilled,\n    rejected: rejected\n  });\n  return this.handlers.length - 1;\n};\n\n// 用来注销指定的拦截器\nInterceptorManager.prototype.eject = function eject(id) {\n  if (this.handlers[id]) {\n    this.handlers[id] = null;\n  }\n};\n\n// 遍历this.handlers，并将this.handlers里的每一项作为参数传给fn执行\nInterceptorManager.prototype.forEach = function forEach(fn) {\n  utils.forEach(this.handlers, function forEachHandler(h) {\n    if (h !== null) {\n      fn(h);\n    }\n  });\n};\n\n\n```\n\n那么当我们通过`axios.interceptors.request.use`添加拦截器后，\naxios内部又是怎么让这些拦截器能够在请求前、请求后拿到我们想要的数据的呢？\n\n先看下代码：\n\n```javascript\n\n// /lib/core/Axios.js\nAxios.prototype.request = function request(config) {\n  // ...\n  var chain = [dispatchRequest, undefined];\n\n  // 初始化一个promise对象，状态为resolved，接收到的参数为config对象\n  var promise = Promise.resolve(config);\n\n  // 注意：interceptor.fulfilled 或 interceptor.rejected 是可能为undefined\n  this.interceptors.request.forEach(function unshiftRequestInterceptors(interceptor) {\n    chain.unshift(interceptor.fulfilled, interceptor.rejected);\n  });\n\n  this.interceptors.response.forEach(function pushResponseInterceptors(interceptor) {\n    chain.push(interceptor.fulfilled, interceptor.rejected);\n  });\n\n  // 添加了拦截器后的chain数组大概会是这样的：\n  // [\n  //   requestFulfilledFn, requestRejectedFn, ..., \n  //   dispatchRequest, undefined,\n  //   responseFulfilledFn, responseRejectedFn, ....,\n  // ]\n\n  // 只要chain数组长度不为0，就一直执行while循环\n  while (chain.length) {\n    // 数组的 shift() 方法用于把数组的第一个元素从其中删除，并返回第一个元素的值。\n    // 每次执行while循环，从chain数组里按序取出两项，并分别作为promise.then方法的第一个和第二个参数\n\n    // 按照我们使用InterceptorManager.prototype.use添加拦截器的规则，\n    // 正好每次添加的就是我们通过InterceptorManager.prototype.use方法添加的成功和失败回调\n\n    // 通过InterceptorManager.prototype.use往拦截器数组里添加拦截器时使用的数组的push方法，\n    // 对于请求拦截器:\n    // 从拦截器数组按序读到后是通过unshift方法往chain数组数里添加的，又通过shift方法从chain数组里取出的，\n    // 所以得出结论：对于请求拦截器，先添加的拦截器会后执行\n    // 对于响应拦截器:\n    // 从拦截器数组按序读到后是通过push方法往chain数组里添加的，又通过shift方法从chain数组里取出的，\n    // 所以得出结论：对于响应拦截器，添加的拦截器先执行\n\n    // 第一个请求拦截器的fulfilled函数会接收到promise对象初始化时传入的config对象，\n    // 而请求拦截器又规定用户写的fulfilled函数必须返回一个config对象，\n    // 所以通过promise实现链式调用时，每个请求拦截器的fulfilled函数都会接收到一个config对象\n\n    // 第一个响应拦截器的fulfilled函数会接受到dispatchRequest（也就是我们的请求方法）请求到的数据（也就是response对象）,\n    // 而响应拦截器又规定用户写的fulfilled函数必须返回一个response对象，\n    // 所以通过promise实现链式调用时，每个响应拦截器的fulfilled函数都会接收到一个response对象\n\n    // 任何一个拦截器的抛出的错误，都会被下一个拦截器的rejected函数收到，\n    // 所以dispatchRequest抛出的错误才会被响应拦截器接收到。\n\n    // 因为axios是通过promise实现的链式调用，所以我们可以在拦截器里进行异步操作，\n    // 而拦截器的执行顺序还是会按照我们上面说的顺序执行，\n    // 也就是 dispatchRequest 方法一定会等待所有的请求拦截器执行完后再开始执行，\n    // 响应拦截器一定会等待 dispatchRequest 执行完后再开始执行。\n\n    promise = promise.then(chain.shift(), chain.shift());\n\n  }\n\n  return promise;\n};\n\n```\n\n现在，你应该已经清楚了拦截器是怎么回事，以及拦截器是如何在`Axios.prototype.request`方法里发挥作用的了，\n那么处于\"中游位置\"的`dispatchRequest`是如何发送http请求的呢？\n\n\n### dispatchrequest都做了哪些事\n\ndispatchRequest主要做了3件事：\n1，拿到config对象，对config进行传给http请求适配器前的最后处理；\n2，http请求适配器根据config配置，发起请求\n3，http请求适配器请求完成后，如果成功则根据header、data、和config.transformResponse（关于transformResponse，下面的[数据转换器](#数据转换器-转换请求与响应数据)会进行讲解）拿到数据转换后的response，并return。\n\n```javascript\n\n// /lib/core/dispatchRequest.js\nmodule.exports = function dispatchRequest(config) {\n  throwIfCancellationRequested(config);\n\n  // Support baseURL config\n  if (config.baseURL \u0026\u0026 !isAbsoluteURL(config.url)) {\n    config.url = combineURLs(config.baseURL, config.url);\n  }\n\n  // Ensure headers exist\n  config.headers = config.headers || {};\n\n  // 对请求data进行转换\n  config.data = transformData(\n    config.data,\n    config.headers,\n    config.transformRequest\n  );\n\n  // 对header进行合并处理\n  config.headers = utils.merge(\n    config.headers.common || {},\n    config.headers[config.method] || {},\n    config.headers || {}\n  );\n\n  // 删除header属性里无用的属性\n  utils.forEach(\n    ['delete', 'get', 'head', 'post', 'put', 'patch', 'common'],\n    function cleanHeaderConfig(method) {\n      delete config.headers[method];\n    }\n  );\n\n  // http请求适配器会优先使用config上自定义的适配器，没有配置时才会使用默认的XHR或http适配器，\n  // 不过大部分时候，axios提供的默认适配器是能够满足我们的\n  var adapter = config.adapter || defaults.adapter;\n\n  return adapter(config).then(/**/);\n};\n\n```\n\n好了，看到这里，我们是时候梳理一下：axios是如何用promise搭起基于xhr的异步桥梁的？\n\n\n### axios是如何用promise搭起基于xhr的异步桥梁的\naxios是如何通过Promise进行异步处理的？\n\n#### 如何使用\n\n```javascript\n\nimport axios from 'axios'\n\naxios.get(/**/)\n.then(data =\u003e {\n  // 此处可以拿到向服务端请求回的数据\n})\n.catch(error =\u003e {\n  // 此处可以拿到请求失败或取消或其他处理失败的错误对象\n})\n\n```\n\n#### 源码分析\n\n先来一个图简单的了解下axios项目里，http请求完成后到达用户的顺序流：\n\n![](promise-tree.png)\n\n通过[axios为何会有多种使用方式](#axios为何会有多种使用方式)我们知道，\n用户无论以什么方式调用axios，最终都是调用的`Axios.prototype.request`方法，\n这个方法最终返回的是一个Promise对象。\n\n```javascript\n\nAxios.prototype.request = function request(config) {\n  // ...\n  var chain = [dispatchRequest, undefined];\n  // 将config对象当作参数传给Primise.resolve方法\n  var promise = Promise.resolve(config);\n\n  while (chain.length) {\n    promise = promise.then(chain.shift(), chain.shift());\n  }\n\n  return promise;\n};\n\n```\n\n`Axios.prototype.request`方法会调用`dispatchRequest`方法，而`dispatchRequest`方法会调用`xhrAdapter`方法，`xhrAdapter`方法返回的是还一个Promise对象\n\n```javascript\n\n// /lib/adapters/xhr.js\nfunction xhrAdapter(config) {\n  return new Promise(function dispatchXhrRequest(resolve, reject) {\n    // ... 省略代码\n  });\n};\n\n```\n\n`xhrAdapter`内的XHR发送请求成功后会执行这个Promise对象的`resolve`方法,并将请求的数据传出去,\n反之则执行`reject`方法，并将错误信息作为参数传出去。\n\n```javascript\n\n// /lib/adapters/xhr.js\nvar request = new XMLHttpRequest();\nvar loadEvent = 'onreadystatechange';\n\nrequest[loadEvent] = function handleLoad() {\n  // ...\n  // 往下走有settle的源码\n  settle(resolve, reject, response);\n  // ...\n};\nrequest.onerror = function handleError() {\n  reject(/**/);\n  request = null;\n};\nrequest.ontimeout = function handleTimeout() {\n  reject(/**/);\n  request = null;\n};\n\n```\n\n验证服务端的返回结果是否通过验证：\n\n```javascript\n\n// /lib/core/settle.js\nfunction settle(resolve, reject, response) {\n  var validateStatus = response.config.validateStatus;\n  if (!response.status || !validateStatus || validateStatus(response.status)) {\n    resolve(response);\n  } else {\n    reject(/**/);\n  }\n};\n\n```\n\n回到`dispatchRequest`方法内，首先得到`xhrAdapter`方法返回的Promise对象,\n然后通过`.then`方法，对`xhrAdapter`返回的Promise对象的成功或失败结果再次加工，\n成功的话，则将处理后的`response`返回，\n失败的话，则返回一个状态为`rejected`的Promise对象，\n\n```javascript\n\n  return adapter(config).then(function onAdapterResolution(response) {\n    // ...\n    return response;\n  }, function onAdapterRejection(reason) {\n    // ...\n    return Promise.reject(reason);\n  });\n};\n\n```\n\n那么至此，用户调用`axios()`方法时，就可以直接调用Promise的`.then`或`.catch`进行业务处理了。\n\n回过头来，我们在介绍`dispatchRequest`一节时说到的数据转换，而axios官方也将数据转换专门作为一个亮点来介绍的，那么数据转换到底能在使用axios发挥什么功效呢？\n\n\n### 数据转换器-转换请求与响应数据\n\n#### 如何使用\n\n1. 修改全局的转换器\n\n```javascript\n\nimport axios from 'axios'\n\n// 往现有的请求转换器里增加转换方法\naxios.defaults.transformRequest.push((data, headers) =\u003e {\n  // ...处理data\n  return data;\n});\n\n// 重写请求转换器\naxios.defaults.transformRequest = [(data, headers) =\u003e {\n  // ...处理data\n  return data;\n}];\n\n// 往现有的响应转换器里增加转换方法\naxios.defaults.transformResponse.push((data, headers) =\u003e {\n  // ...处理data\n  return data;\n});\n\n// 重写响应转换器\naxios.defaults.transformResponse = [(data, headers) =\u003e {\n  // ...处理data\n  return data;\n}];\n\n```\n\n2. 修改某次axios请求的转换器\n\n```javascript\n\nimport axios from 'axios'\n\n// 往已经存在的转换器里增加转换方法\naxios.get(url, {\n  // ...\n  transformRequest: [\n    ...axios.defaults.transformRequest, // 去掉这行代码就等于重写请求转换器了\n    (data, headers) =\u003e {\n      // ...处理data\n      return data;\n    }\n  ],\n  transformResponse: [\n    ...axios.defaults.transformResponse, // 去掉这行代码就等于重写响应转换器了\n    (data, headers) =\u003e {\n      // ...处理data\n      return data;\n    }\n  ],\n})\n\n```\n\n#### 源码分析\n\n默认的`defaults`配置项里已经自定义了一个请求转换器和一个响应转换器，\n看下源码：\n\n```javascript\n\n// /lib/defaults.js\nvar defaults = {\n\n  transformRequest: [function transformRequest(data, headers) {\n    normalizeHeaderName(headers, 'Content-Type');\n    // ...\n    if (utils.isArrayBufferView(data)) {\n      return data.buffer;\n    }\n    if (utils.isURLSearchParams(data)) {\n      setContentTypeIfUnset(headers, 'application/x-www-form-urlencoded;charset=utf-8');\n      return data.toString();\n    }\n    if (utils.isObject(data)) {\n      setContentTypeIfUnset(headers, 'application/json;charset=utf-8');\n      return JSON.stringify(data);\n    }\n    return data;\n  }],\n\n  transformResponse: [function transformResponse(data) {\n    if (typeof data === 'string') {\n      try {\n        data = JSON.parse(data);\n      } catch (e) { /* Ignore */ }\n    }\n    return data;\n  }],\n  \n};\n\n```\n\n那么在axios项目里，是在什么地方使用了转换器呢？\n\n请求转换器的使用地方是http请求前，使用请求转换器对请求数据做处理，\n然后传给http请求适配器使用。\n\n```javascript\n\n// /lib/core/dispatchRequest.js\nfunction dispatchRequest(config) {\n  \n  config.data = transformData(\n    config.data,\n    config.headers,\n    config.transformRequest\n  );\n\n  return adapter(config).then(/* ... */);\n};\n\n```\n\n看下`transformData`方法的代码，\n主要遍历转换器数组，分别执行每一个转换器，根据data和headers参数，返回新的data。\n\n```javascript\n\n// /lib/core/transformData.js\nfunction transformData(data, headers, fns) {\n  utils.forEach(fns, function transform(fn) {\n    data = fn(data, headers);\n  });\n  return data;\n};\n\n```\n\n响应转换器的使用地方是在http请求完成后，根据http请求适配器的返回值做数据转换处理：\n\n```javascript\n\n// /lib/core/dispatchRequest.js\nreturn adapter(config).then(function onAdapterResolution(response) {\n    // ...\n    response.data = transformData(\n      response.data,\n      response.headers,\n      config.transformResponse\n    );\n\n    return response;\n  }, function onAdapterRejection(reason) {\n    if (!isCancel(reason)) {\n      // ...\n      if (reason \u0026\u0026 reason.response) {\n        reason.response.data = transformData(\n          reason.response.data,\n          reason.response.headers,\n          config.transformResponse\n        );\n      }\n    }\n\n    return Promise.reject(reason);\n  });\n\n```\n\n#### 转换器和拦截器的关系？\n\n拦截器同样可以实现转换请求和响应数据的需求，但根据作者的设计和综合代码可以看出，\n在请求时，拦截器主要负责修改config配置项，数据转换器主要负责转换请求体，比如转换对象为字符串\n在请求响应后，拦截器可以拿到`response`，数据转换器主要负责处理响应体，比如转换字符串为对象。\n\naxios官方是将\"自动转换为JSON数据\"作为一个独立的亮点来介绍的，那么数据转换器是如何完成这个功能的呢？\n其实非常简单，我们一起看下吧。\n\n\n### 自动转换json数据\n\n在默认情况下，axios将会自动的将传入的data对象序列化为JSON字符串，将响应数据中的JSON字符串转换为JavaScript对象\n\n#### 源码分析\n\n```javascript\n\n// 请求时，将data数据转换为JSON 字符串\n// /lib/defaults.js \ntransformRequest: [function transformRequest(data, headers) {\n  // ...\n    if (utils.isObject(data)) {\n      setContentTypeIfUnset(headers, 'application/json;charset=utf-8');\n      return JSON.stringify(data);\n    }\n    return data;\n}]\n\n// 得到响应后，将请求到的数据转换为JSON对象\n// /lib/defaults.js\ntransformResponse: [function transformResponse(data) {\n    if (typeof data === 'string') {\n      try {\n        data = JSON.parse(data);\n      } catch (e) { /* Ignore */ }\n    }\n    return data;\n}]\n\n```\n\n至此，axios项目的运作流程已经介绍完毕，是不是已经打通了任督二脉了呢\n接下来我们一起看下axios还带给了我们哪些好用的技能点吧。\n\n\n### header设置\n\n#### 如何使用\n\n``` javascript\n\nimport axios from 'axios'\n\n// 设置通用header\naxios.defaults.headers.common['X-Requested-With'] = 'XMLHttpRequest'; // xhr标识\n\n// 设置某种请求的header\naxios.defaults.headers.post['Content-Type'] = 'application/x-www-form-urlencoded;charset=utf-8';\n\n// 设置某次请求的header\naxios.get(url, {\n  headers: {\n    'Authorization': 'whr1',\n  },\n})\n\n```\n\n#### 源码分析\n\n``` javascript\n\n// /lib/core/dispatchRequest.js  -  44行\n\n  config.headers = utils.merge(\n    config.headers.common || {},\n    config.headers[config.method] || {},\n    config.headers || {}\n  );\n\n```\n\n\n### 如何取消已经发送的请求\n\n#### 如何使用\n\n```javascript\n\nimport axios from 'axios'\n\n// 第一种取消方法\naxios.get(url, {\n  cancelToken: new axios.CancelToken(cancel =\u003e {\n    if (/* 取消条件 */) {\n      cancel('取消日志');\n    }\n  })\n});\n\n// 第二种取消方法\nconst CancelToken = axios.CancelToken;\nconst source = CancelToken.source();\naxios.get(url, {\n  cancelToken: source.token\n});\nsource.cancel('取消日志');\n\n```\n\n#### 源码分析\n\n```javascript\n\n// /cancel/CancelToken.js  -  11行\nfunction CancelToken(executor) {\n \n  var resolvePromise;\n  this.promise = new Promise(function promiseExecutor(resolve) {\n    resolvePromise = resolve;\n  });\n  var token = this;\n  executor(function cancel(message) {\n    if (token.reason) {\n      return;\n    }\n    token.reason = new Cancel(message);\n    resolvePromise(token.reason);\n  });\n}\n\n// /lib/adapters/xhr.js  -  159行\nif (config.cancelToken) {\n    config.cancelToken.promise.then(function onCanceled(cancel) {\n        if (!request) {\n            return;\n        }\n        request.abort();\n        reject(cancel);\n        request = null;\n    });\n}\n\n```\n\n取消功能的核心是通过CancelToken内的`this.promise = new Promise(resolve =\u003e resolvePromise = resolve)`，\n得到实例属性`promise`，此时该`promise`的状态为`pending`\n通过这个属性，在`/lib/adapters/xhr.js`文件中继续给这个`promise`实例添加`.then`方法\n（`xhr.js`文件的159行`config.cancelToken.promise.then(message =\u003e request.abort())`）；\n\n在`CancelToken`外界，通过`executor`参数拿到对`cancel`方法的控制权，\n这样当执行`cancel`方法时就可以改变实例的`promise`属性的状态为`rejected`，\n从而执行`request.abort()`方法达到取消请求的目的。\n\n上面第二种写法可以看作是对第一种写法的完善，\n因为很多是时候我们取消请求的方法是用在本次请求方法外，\n例如，发送A、B两个请求，当B请求成功后，取消A请求。\n\n```javascript\n\n// 第1种写法：\nlet source;\naxios.get(Aurl, {\n  cancelToken: new axios.CancelToken(cancel =\u003e {\n    source = cancel;\n  })\n});\naxios.get(Burl)\n.then(() =\u003e source('B请求成功了'));\n\n// 第2种写法：\nconst CancelToken = axios.CancelToken;\nconst source = CancelToken.source();\naxios.get(Aurl, {\n  cancelToken: source.token\n});\naxios.get(Burl)\n.then(() =\u003e source.cancel('B请求成功了'));\n\n```\n\n相对来说，我更推崇第1种写法，因为第2种写法太隐蔽了，不如第一种直观好理解。\n\n\n##### 发现的问题\n\n1. /lib/adapters/xhr.js文件中，onCanceled方法的参数不应该叫message么，为什么叫cancel？\n\n2. /lib/adapters/xhr.js文件中，onCanceled方法里，reject里应该将config信息也传出来\n\n\n### 跨域携带cookie\n\n#### 如何使用\n\n```javascript \n\nimport axios from 'axios'\n\naxios.defaults.withCredentials = true;\n\n```\n\n#### 源码分析\n\n我们在[用户配置的config是怎么起作用的](#用户配置的config是怎么起作用的)一节已经介绍了config在axios项目里的传递过程，\n由此得出，我们通过`axios.defaults.withCredentials = true`做的配置，\n在`/lib/adapters/xhr.js`里是可以取到的，然后通过以下代码配置到xhr对象项。\n\n```javascript\n\nvar request = new XMLHttpRequest();\n\n// /lib/adapters/xhr.js\nif (config.withCredentials) {\n  request.withCredentials = true;\n}\n\n```\n\n\n### 超时配置及处理\n\n#### 如何使用\n\n```javascript\n\nimport axios from 'axios'\n\naxios.defaults.timeout = 3000;\n\n```\n\n#### 源码分析\n\n```javascript\n\n// /adapters/xhr.js\nrequest.timeout = config.timeout;\n\n// /adapters/xhr.js\n// 通过createError方法，将错误信息合为一个字符串\nrequest.ontimeout = function handleTimeout() {\n  reject(createError('timeout of ' + config.timeout + 'ms exceeded', \n    config, 'ECONNABORTED', request));\n};\n\n```\n\n-   axios库外如何添加超时后的处理\n\n```javascript\n\naxios().catch(error =\u003e {\n  const { message } = error;\n  if (message.indexOf('timeout') \u003e -1){\n    // 超时处理\n  }\n})\n\n```\n\n\n### 改写验证成功或失败的规则validatestatus\n自定义http状态码的成功、失败范围\n\n#### 如何使用\n\n```javascript\n\nimport axios from 'axios'\n\naxios.defaults.validateStatus = status =\u003e status \u003e= 200 \u0026\u0026 status \u003c 300;\n\n```\n\n#### 源码分析\n\n在默认配置中，定义了默认的http状态码验证规则，\n所以自定义`validateStatus`其实是对此处方法的重写\n\n```javascript\n\n// `/lib/defaults.js`\nvar defaults = {\n  // ...\n  validateStatus: function validateStatus(status) {\n    return status \u003e= 200 \u0026\u0026 status \u003c 300;\n  },\n  // ...\n}\n\n```\n\naxios是何时开始验证http状态码的？\n\n```javascript\n\n// /lib/adapters/xhr.js\nvar request = new XMLHttpRequest();\nvar loadEvent = 'onreadystatechange';\n\n// /lib/adapters/xhr.js\n// 每当 readyState 改变时，就会触发 onreadystatechange 事件\nrequest[loadEvent] = function handleLoad() {\n  if (!request || (request.readyState !== 4 \u0026\u0026 !xDomain)) {\n    return;\n  }\n  // ...省略代码\n  var response = {\n      // ...\n      // IE sends 1223 instead of 204 (https://github.com/axios/axios/issues/201)\n      status: request.status === 1223 ? 204 : request.status,\n      config: config,\n  };\n  settle(resolve, reject, response);\n  // ...省略代码\n}\n\n```\n\n```javascript\n\n// /lib/core/settle.js\nfunction settle(resolve, reject, response) {\n  // 如果我们往上捣一捣就会发现，config对象的validateStatus就是我们自定义的validateStatus方法或默认的validateStatus方法\n  var validateStatus = response.config.validateStatus;\n  // validateStatus验证通过，就会触发resolve方法\n  if (!response.status || !validateStatus || validateStatus(response.status)) {\n    resolve(response);\n  } else {\n    reject(createError(\n      'Request failed with status code ' + response.status,\n      response.config,\n      null,\n      response.request,\n      response\n    ));\n  }\n};\n\n```\n\n\n### 总结\n\naxios这个项目里，有很多对JS使用很巧妙的地方，比如对promise的串联操作（当然你也可以说这块是借鉴很多异步中间件的处理方式）,让我们可以很方便对请求前后的各种处理方法的流程进行控制；很多实用的小优化，比如请求前后的数据处理，省了程序员一遍一遍去写JSON.xxx了；同时支持了浏览器和node两种环境，对使用node的项目来说无疑是极好的。\n\n总之，这个能够在github斩获42K+（截止2018.05.27）的star，实力绝不是概的，值得好好交交心！\n","funding_links":[],"categories":[],"sub_categories":[],"project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fronffy%2Faxios-tutorial","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fronffy%2Faxios-tutorial","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fronffy%2Faxios-tutorial/lists"}