Wang's blog Wang's blog
首页
  • 前端文章

    • HTML教程
    • CSS
    • JavaScript
  • 前端框架

    • Vue
    • React
    • VuePress
    • Electron
  • 后端技术

    • Npm
    • Node
    • TypeScript
  • 编程规范

    • 规范
  • 我的笔记
  • Git
  • GitHub
  • VSCode
  • Mac工具
  • 数据库
  • Google
  • 服务器
  • Python爬虫
  • 前端教程
更多
收藏
关于
  • 分类
  • 标签
  • 归档
GitHub (opens new window)

Wang Mings

跟随大神,成为大神!
首页
  • 前端文章

    • HTML教程
    • CSS
    • JavaScript
  • 前端框架

    • Vue
    • React
    • VuePress
    • Electron
  • 后端技术

    • Npm
    • Node
    • TypeScript
  • 编程规范

    • 规范
  • 我的笔记
  • Git
  • GitHub
  • VSCode
  • Mac工具
  • 数据库
  • Google
  • 服务器
  • Python爬虫
  • 前端教程
更多
收藏
关于
  • 分类
  • 标签
  • 归档
GitHub (opens new window)
  • Python爬虫

  • 前端教程

    • 团队规范

    • Project

      • Axios用法与原理
        • [#](#特性) 特性
        • [#](#api) API
        • [#](#为何axios有如此多使用方式) 为何axios有如此多使用方式
        • [#](#整体流程) 整体流程
        • [#](#智能的默认设置和扩展) 智能的默认设置和扩展
        • [#](#总结) 总结
      • css布局技巧
      • 深度学习平台术语
      • 谈谈前端天花板问题
      • 一个程序员的成长之路
      • Markdown-It 解析原理
      • minipack源码解析
      • PostCSS
      • Electron工程踩坑记录
      • H5 Video踩坑记录
      • Puppeteer翻页爬虫
      • 重构你的javascript代码
      • RxJS入门实践
      • 官网脚手架思考与实践
      • Stylelint样式规范工具
      • TypeScript开发Vue应用
      • Typescript tsconfig.json全解析
      • Vue项目TypeScript指南
      • TypeScript在Vue2.x中的坑
      • Vue Dialog弹窗解决方案
      • Vue JSX插件依赖及语法实践
      • Webpack 模块打包原理
      • Webpack4 配置详解
      • Webpack4 devServer配置详解
      • Webpack3.x升级Webpack4指南
    • JS

    • NodeJS

    • Vue

    • React

    • 效率工具

    • 读书笔记

  • 教程
  • 前端教程
  • Project
wangmings
2022-07-19
目录

Axios用法与原理

# # Axios用法与原理

axios是一个非常小巧而好用的http请求库,支持promise以及同时支持浏览器和node端。axios使用简单,配置灵活,也是vue官方推荐的请求库。另外axios源码 (opens new window) (opens new window)层次清晰明了,非常适合阅读。

# # 特性

  • 从浏览器中创建 XMLHttpRequest
  • 从 node.js 发出 http 请求
  • 支持 Promise API
  • 拦截请求和响应
  • 转换请求和响应数据
  • 取消请求
  • 自动转换JSON数据
  • 客户端支持防止 CSRF/XSRF

# # API

  • 全局
    • axios.request(config) 最终http请求都是执行这个方法
    • axios(config) 和axios.request()等价
    • axios(url[, config]) axios(config)快捷方式
    • axios.[METHODS](url, config) axios(config)快捷方式
  • 自定义实例
    • axios.create(config) 自定义配置,创建实例instance。调用方式和axios方法一致
  • 拦截器
    • axios.interceptors.request.use
    • axios.interceptors.response.use
    // 以下实例等价
    // 全局调用
    axios({
      method:'get',
      url:'http://bit.ly/2mTM3nY',
      field: 123
    }) // axios(config)
    axios('http://bit.ly/2mTM3nY', {field: 123}) // axios(url[, config])
    axios.get('http://bit.ly/2mTM3nY', {field: 123}) // axios.[METHODS](url, config)
    
    // 自定义实例调用
    const instance = axios.create({
      baseURL: 'http://bit.ly'
    });
    instance({
      method:'get',
      url:'2mTM3nY',
      field: 123
    }) // instance(config)
    instance.get('2mTM3nY', {field: 123}) // instance.[METHODS](url, config)
    
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21

配置优先级:lib / defaults.js中的库默认值 -->实例的config属性--> 请求的config参数

# # 为何axios有如此多使用方式

重点是createInstance方法,该方法拿到一个Function,该Function指向请求入口Axios.prototype.request,并且该Function还继承了Axios.prototype的每个方法,并且上下文指向同一个对象context。axios包默认导出是该Function,而自定义实例axios.create是一个工厂模式,最终都调用createInstance方法。

源码在lib/default.js中:

    function createInstance(defaultConfig) {
      var context = new Axios(defaultConfig);
      // instance指向了request方法,且上下文指向context
      // instance(config) = Axios.prototype.request(config)
      var instance = bind(Axios.prototype.request, context);
    
      // 把Axios.prototype上的方法扩展到instance对象上
      // 这样 instance 就有了 get、post、put等METHOD方法
      // 同时指定上下文为context,这样执行Axios原型链上的方法时,this会指向context
      utils.extend(instance, Axios.prototype, context);
    
      // 把context对象上的自身属性和方法扩展到instance上
      utils.extend(instance, context);
    
      return instance;
    }
    
    // 导出时就创建一个默认实例,所以可以通过axios(config)发出请求
    var axios = createInstance(defaults);
    axios.Axios = Axios;
    
    // 工厂模式创建axios实例,其实最终都是调用createInstance方法。
    // 所以实例调用方式和全局axios调用方式相同。instance(config) = axios(config)
    axios.create = function create(instanceConfig) {
      return createInstance(mergeConfig(axios.defaults, instanceConfig));
    };
    module.exports = axios;
    module.exports.default = axios; // 允许在ts中导入
    
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29

# # 整体流程

请求流程实质上无非就是输入和输出,输入request config配置(如url、method、data等),输出最终的response data数据。中间涉及到较多的数据预处理,如根据data数据类型(如JSON),设置Content-Type: json头信息;再如根据响应的数据类型,默认转换为json格式。这些预处理需要axios库自动完成,同时也要暴露出相关配置给开发者,以此实现开发者自定义预处理。

Axios通过拦截者(Interceptors)来实现上面说的数据预处理,具体通过使用chain链式来逐一插入处理逻辑。默认情况下,先拿到此次请求的config配置,把config作为参数,传递到dispatchRequest方法中,该方法会根据当前环境(Browser/Node)来调用不同的adapter发送请求,以此获得最终的response data。同时Axios还允许使用request/response Interceptors进行请求前和响应后的自定义处理,其实质是将预处理函数,插入chain数组中,再统一按照顺序逐步执行预处理方法(Promise保证顺序)。

Axios类是核心内容,该类request方法是所有请求的开始入口。源码在lib/core/Axios.js:

    Axios.prototype.request = function request(config) {
      // 1. 允许 axios('url'[, config]) = axios(config)
      if (typeof config === 'string') {
        config = arguments[1] || {};
        config.url = arguments[0];
      } else {
        config = config || {};
      }
    
      // 配置文件合并策略优先级
      config = mergeConfig(this.defaults, config);
      config.method = config.method ? config.method.toLowerCase() : 'get';
    
      // 2. 定义拦截器中间件钩子
      // dispatchRequest是真正开始下发请求,执行config中设置的adapter方法
      var chain = [dispatchRequest, undefined];
      var promise = Promise.resolve(config);
      // 添加请求前钩子
      this.interceptors.request.forEach(function unshiftRequestInterceptors(interceptor) {
        chain.unshift(interceptor.fulfilled, interceptor.rejected);
      });
      // 添加请求后钩子
      this.interceptors.response.forEach(function pushResponseInterceptors(interceptor) {
        chain.push(interceptor.fulfilled, interceptor.rejected);
      });
    
      // 3. chain链关键代码,promise链路传递下去
      while (chain.length) {
        promise = promise.then(chain.shift(), chain.shift());
      }
    
      return promise;
    };
    
    // 提供对request方法的METHOD快捷方式,实质都是调用Axios.prototype.request方法
    // axios.get(url, config) = axios(config)
    utils.forEach(['delete', 'get', 'head', 'options'], function forEachMethodNoData(method) {
      Axios.prototype[method] = function(url, config) {
        return this.request(utils.merge(config || {}, {
          method: method,
          url: url
        }));
      };
    });
    
    utils.forEach(['post', 'put', 'patch'], function forEachMethodWithData(method) {
      Axios.prototype[method] = function(url, data, config) {
        return this.request(utils.merge(config || {}, {
          method: method,
          url: url,
          data: data
        }));
      };
    });
    
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55

# # 智能的默认设置和扩展

  • 根据环境自动设置请求的adapter,同时支持自定义
  • 自动根据请求data数据类型,设置headers。比如Content-Type自动设置
  • 自动根据响应data数据类型,转为json
  • 支持自定义转换请求和响应数据

源码在lib/default.js中

    var defaults = {
      // 根据环境选择默认的请求方式。支持node和浏览器,也可以自定义adapter
      adapter: getDefaultAdapter(),
      // 请求转换
      transformRequest: [function transformRequest(data, headers) {
        normalizeHeaderName(headers, 'Accept');
        normalizeHeaderName(headers, 'Content-Type');
        if (utils.isFormData(data) ||
          utils.isArrayBuffer(data) ||
          utils.isBuffer(data) ||
          utils.isStream(data) ||
          utils.isFile(data) ||
          utils.isBlob(data)
        ) {
          return data;
        }
        if (utils.isArrayBufferView(data)) {
          return data.buffer;
        }
        if (utils.isURLSearchParams(data)) {
          setContentTypeIfUnset(headers, 'application/x-www-form-urlencoded;charset=utf-8');
          return data.toString();
        }
        if (utils.isObject(data)) { // 对象数据时,自动设置Content-Type为json格式。
          setContentTypeIfUnset(headers, 'application/json;charset=utf-8');
          return JSON.stringify(data);
        }
        return data;
      }],
      // 响应转换
      transformResponse: [function transformResponse(data) {
        /*eslint no-param-reassign:0*/
        if (typeof data === 'string') {
          try {
            data = JSON.parse(data);
          } catch (e) { /* Ignore */ }
        }
        return data;
      }],
    
      timeout: 0,
      xsrfCookieName: 'XSRF-TOKEN',
      xsrfHeaderName: 'X-XSRF-TOKEN',
      maxContentLength: -1,
      validateStatus: function validateStatus(status) {
        return status >= 200 && status < 300;
      }
    };
    
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49

# # 总结

请求从用户调用看,只是设置好config配置,剩下的具体底层调用应该交给专业库去做。axios库就是其中佼佼者,它较好的封装了http请求流程,支持多种形式config merge,通过抽象adapter层(通过是否有XMLHttpRequest对象来判断),来兼容浏览器以及Node环境。通过chain链式方式,不仅预处理请求/响应数据,同时支持用户自定义拦截处理(Interceptors),以此实现用户简约调用。

编辑 (opens new window)
推荐-Vue-Router写法
css布局技巧

← 推荐-Vue-Router写法 css布局技巧→

最近更新
01
theme-vdoing-blog博客静态编译问题
09-16
02
搜索引擎
07-19
03
友情链接
07-19
更多文章>
Theme by Vdoing | Copyright © 2019-2022 Evan Xu | MIT License
  • 跟随系统
  • 浅色模式
  • 深色模式
  • 阅读模式