技术分享
首页
  • JavaScript

    • 构造函数和原型
    • Cookie和Session
    • Object.create(null)和{}
    • TypeScript配置
    • typescript入门到进阶
  • 框架

    • Vue-Router
    • React基础入门
  • 其它

    • Http协议
    • 跨域问题总结
  • 分析Promise实现
  • Axios源码分析
  • Webpack原理
  • vueRouter源码分析
  • Vue

    • Vite快速搭建Vue3+TypeScript项目
    • Cordova打包Vue项目的问题
    • Vue将汉字转为拼音,取出首字母
    • h5项目问题总结
  • JavaScript

    • new Function
  • 后端

    • Node.js中使用Crypto生成Token
    • Body-Parser处理多层对象的问题
  • 其它

    • 项目Demo汇总
    • Vuepress+Vercel搭建个人站点
    • 项目中能用到的
    • husky规范代码提交
  • Mongoose基础
  • Multer文件上传中间件的使用
  • JavaScript

    • 浅谈两数全等
    • JavaScript进制转换
    • 手写bind,apply,call和new
  • 算法

    • 数组去重和排序
    • 数组扁平化
    • 斐波那契数列
  • JavaScript 数据结构
  • 其它

    • webpack面试题
    • vite面试题
    • svg和canvas的优缺点
    • TypeScript面试题
    • Vue常见面试题
  • 计算机网络

    • 数据链路层
    • 网络层
  • Git的使用
  • Nginx的使用
  • CentOS7安装Nginx
  • 正则表达式
  • SEO搜索引擎优化
  • Serverless介绍
友链
GitHub (opens new window)

刘誉

总有人要赢,为什么不能是我
首页
  • JavaScript

    • 构造函数和原型
    • Cookie和Session
    • Object.create(null)和{}
    • TypeScript配置
    • typescript入门到进阶
  • 框架

    • Vue-Router
    • React基础入门
  • 其它

    • Http协议
    • 跨域问题总结
  • 分析Promise实现
  • Axios源码分析
  • Webpack原理
  • vueRouter源码分析
  • Vue

    • Vite快速搭建Vue3+TypeScript项目
    • Cordova打包Vue项目的问题
    • Vue将汉字转为拼音,取出首字母
    • h5项目问题总结
  • JavaScript

    • new Function
  • 后端

    • Node.js中使用Crypto生成Token
    • Body-Parser处理多层对象的问题
  • 其它

    • 项目Demo汇总
    • Vuepress+Vercel搭建个人站点
    • 项目中能用到的
    • husky规范代码提交
  • Mongoose基础
  • Multer文件上传中间件的使用
  • JavaScript

    • 浅谈两数全等
    • JavaScript进制转换
    • 手写bind,apply,call和new
  • 算法

    • 数组去重和排序
    • 数组扁平化
    • 斐波那契数列
  • JavaScript 数据结构
  • 其它

    • webpack面试题
    • vite面试题
    • svg和canvas的优缺点
    • TypeScript面试题
    • Vue常见面试题
  • 计算机网络

    • 数据链路层
    • 网络层
  • Git的使用
  • Nginx的使用
  • CentOS7安装Nginx
  • 正则表达式
  • SEO搜索引擎优化
  • Serverless介绍
友链
GitHub (opens new window)
  • 分析Promise实现
  • Axios源码分析
    • XMLHttpRequest 简单使用
      • 介绍
      • 封装 Ajax
    • Node 的 http 模块 使用
    • Axios
      • 目录结构
      • axios.js
      • core
      • cancel
      • adapters
  • vueRouter源码分析
  • Webpack原理
  • 阅读《重构:改善既有代码的设计》笔记
  • vue源码
  • webpack
  • 可视化
  • koa全家桶
  • 笔记
coderly
2021-03-25

Axios源码分析

# Axios 源码分析

从封装自己的 Ajax,再到 axios 源码。
总体来说,axios 在配置项上做了很多
我们能学习的地方不仅仅在于如何封装,同等重要的还有它实现的技巧

# XMLHttpRequest 简单使用

# 介绍

  • 实例
let xhr = new XMLHttpRequest()
1
  • 发起 http 请求
xhr.open('GET', '/api/data')
xhr.send()
1
2
  • 设置回调函数
xhr.onreadystatechange = function() {
  // readyState: xhr 对象的状态
  // status:xhr 响应的状态
  if (xhr.readyState == 4 && xhr.status == 200) {
    alert(xhr.responseText)
  } else {
    alert(xhr.statusText)
  }
}
1
2
3
4
5
6
7
8
9

# 封装 Ajax

封装自己的 Ajax

  const fn = (res) => { }
  let { method = 'GET', url, data, onSuccess = fn, onError = fn, type = '' } = options || {}
  // 创建兼容 XMLHttpRequest 对象
  let xhr
  if (window.XMLHttpRequest) {
    xhr = new XMLHttpRequest()
  } else {
    xhr = new ActiveXObject('Microsoft.XMLHTTP')
  }

  // 给请求添加状态变化事件处理函数
  xhr.onreadystatechange = function () {
    // 判断状态码
    if (xhr.readyState == 4) {
      if (xhr.status == 200) {
        let res
        // 根据type参数,判断返回的内容需要进行怎样的处理
        if (type == 'json') {
          // 获得 json 形式的响应数据,并使用parse方法解析
          res = JSON.parse(xhr.responseText)
        } else if (type == 'xml') {
          // 获得 XML 形式的响应数据
          res = xhr.responseXML
        } else {
          // 获得字符串形式的响应数据
          res = xhr.responseText
        }
        onSuccess(res) // 成功回调
      } else {
        onError(xhr.responseText)
      }
    }
  }

  // 判断data是否有数据
  let param = ''
  if (data) {
    if (JSON.stringify(data) != '{}') { // 这里使用stringify方法将js对象格式化为json字符串
      url += '?'
      for (let i in data) {
        param += i + '=' + data[i] + '&'   // 将js对象重组,拼接成url参数存入param变量中
      }
      // 去除拼接的最后一个&字符
      param = param.slice(0, param.length - 1)
    }
  }


  // 判断method是否为get
  if (method == 'get') {
    // 是则将数据拼接在url后面
    url = url + param
  }


  // 初始化请求
  xhr.open(method, url, true)

  // 如果method == post
  if (method == 'post') {
    xhr.setRequestHeader('Content-Type', 'application/x-www-form-urlencoded')
    //发送请求
    xhr.send(param)
  } else {
    // 发送请求
    xhr.send(null)
  }
}
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
56
57
58
59
60
61
62
63
64
65
66
67
68

验证

myAjax({
  method: 'get',
  url: 'https://coderly.cn/',
  type: '',
  onSuccess(result) {
    console.log('result', result)
  },
  onError(error) {
    console.log('error', error)
  },
})
1
2
3
4
5
6
7
8
9
10
11
Axios-封装ajax效果图片

# Node 的 http 模块 使用

const http = require('http')
http
  .request(
    {
      url: 'http://192.168.1.2/index.html',
      method: 'GET',
    },
    (response) => {
      let result = ''

      response.on('data', (chunk) => {
        result += chunk
      })

      response.on('end', () => {
        console.log('Success:', result)
      })
    }
  )
  .on('error', (error) => {
    console.log('Error:' + error.message)
  })
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22

# Axios

版本:v0.20.0

# 目录结构

|- examples
|- lib
  |- adapters # 真实发起请求的(node的http模块和XMLHttpRequest做兼容node环境和浏览器环境)
    |- http.js
    |- xhr.js
  |- cancel # 取消请求的操作
  |- core # axios 核心功能
    |- Axios.js # Axios 构造函数
    |- buildFullPath.js
    |- createError.js
    |- dispatchRequest.js # request() 实际发起请求的函数(是adapters兼容性处理)
    |- enhanceError.js
    |- InterceptorManager.js
    |- mergeConfig.js # config 配置项合并函数
    |- settle.js
    |- transformData.js
  |- helpers # 辅助工具,一般和解析、url、cookie等有关
  |- axios.js # 导出 axios 对象以及在该对象上添加额外方法
  |- defaults.js # axios 内部默认配置
  |- utils.js # 常用转换(判断)工具
|- sandbox
|- index.js # 仅导出文件
|- package.json
|- webpack.config.js
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24

# axios.js

非构造函数
负责创建实例额外添加方法并导出

function createInstance(defaultConfig) {
  var context = new Axios(defaultConfig) // 创建实例
  var instance = bind(Axios.prototype.request, context) // bind 返回一个函数,直接执行 instance 函数将会代理到 Axios 原型上的 request 方法上 例如:axios() -> 调用的是 Axios.prototype.request()

  utils.extend(instance, Axios.prototype, context) // 将 axios.prototype 上属性和方法 复制到 instance 函数上 并将 this 指向 context 对象

  utils.extend(instance, context) // 将 context 上属性和方法 复制到 instance 函数上

  return instance
}

var axios = createInstance(defaults) // 调用创建实例函数

axios.Axios = Axios // 公开Axios类以允许类继承

axios.create = function create(instanceConfig) {
  // 工厂函数,返回一个新对象,常用在
  return createInstance(mergeConfig(axios.defaults, instanceConfig))
}

// 添加创建取消请求的函数
axios.Cancel = require('./cancel/Cancel')
axios.CancelToken = require('./cancel/CancelToken')
axios.isCancel = require('./cancel/isCancel')

axios.all = function all(promises) {
  // axios.all 调用的是 Promise.all
  return Promise.all(promises)
}
axios.spread = require('./helpers/spread') // 函数柯里化

module.exports = axios

module.exports.default = axios // 允许在TypeScript中使用默认导入语法
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

# core

# Axios.js

Axios 构造函数

  • this.interceptors.request(response).use 说明
function InterceptorManager() {
  this.handlers = []
}
// 调用use,相当于往数组中放入了一个对象(fulfilled, rejected)
// 请求拦截器:
// axios取数据时,从第一个往后遍历,插入到 chain 链的前面
// 所以最后执行顺序是:后调用 use 的 先执行

// 响应拦截器:
// axios取数据时,从第一个往后遍历,插入到 chain 链的后面
// 所以最后执行顺序是:先调用 use 的 先执行
InterceptorManager.prototype.use = function use(fulfilled, rejected) {
  this.handlers.push({
    fulfilled: fulfilled,
    rejected: rejected,
  })
  return this.handlers.length - 1
}

InterceptorManager.prototype.eject = function eject(id) {
  if (this.handlers[id]) {
    this.handlers[id] = null
  }
}

InterceptorManager.prototype.forEach = function forEach(fn) {
  utils.forEach(this.handlers, function forEachHandler(h) {
    if (h !== null) {
      fn(h)
    }
  })
}
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

用一张图表示如下

Axios 拦截器图片
  • Axios 构造函数
  • 给构造函数原型添加各种方法
  • 通过 chain 数组,控制执行顺序(对请求前,响应后做一些处理)
  • 最终所有类型方法请求都是在调用 request 方法
function Axios(instanceConfig) {
  // Axios 构造函数
  this.defaults = instanceConfig
  this.interceptors = {
    // 拦截器
    request: new InterceptorManager(),
    response: new InterceptorManager(),
  }
}

Axios.prototype.request = function request(config) {
  // 发起请求最重要的方法(最后各类请求都会调用该方法)
  if (typeof config === 'string') {
    config = arguments[1] || {}
    config.url = arguments[0]
  } else {
    config = config || {}
  }

  config = mergeConfig(this.defaults, config) // 合并配置

  // 设置 config.method
  if (config.method) {
    config.method = config.method.toLowerCase()
  } else if (this.defaults.method) {
    config.method = this.defaults.method.toLowerCase()
  } else {
    config.method = 'get'
  }

  // 连接拦截器中间件
  var chain = [dispatchRequest, undefined] // dispatchRequest 为真实发起请求的函数,dispatchRequest 前为请求拦截器,后是响应拦截器
  var promise = Promise.resolve(config) // 将 config 参数作为值 在 chain 链中传递(每个拦截器都必须将return config|Error)

  this.interceptors.request.forEach(function unshiftRequestInterceptors(
    interceptor
  ) {
    chain.unshift(interceptor.fulfilled, interceptor.rejected) // 添加请求拦截,[fulfilled, rejected]
  })

  this.interceptors.response.forEach(function pushResponseInterceptors(
    interceptor
  ) {
    chain.push(interceptor.fulfilled, interceptor.rejected) // 添加响应拦截,[fulfilled, rejected]
  })

  while (chain.length) {
    promise = promise.then(chain.shift(), chain.shift()) // 每次取出两个用 Promise 将 chain 链串联起来,[resolve, reject]
  }

  return promise
}

Axios.prototype.getUri = function getUri(config) {
  config = mergeConfig(this.defaults, config)
  return buildURL(config.url, config.params, config.paramsSerializer).replace(
    /^\?/,
    ''
  )
}

// utils.forEach 其实就是 遍历的方法, 在Axios 原型上添加 delete, get, head, options 方法
utils.forEach(
  ['delete', 'get', 'head', 'options'],
  function forEachMethodNoData(method) {
    Axios.prototype[method] = function(url, config) {
      return this.request(
        // 最后调用的是 Axios.prototype.request 方法 看上↑↑↑
        mergeConfig(config || {}, {
          method: method,
          url: url,
        })
      )
    }
  }
)
// 同上
utils.forEach(['post', 'put', 'patch'], function forEachMethodWithData(method) {
  Axios.prototype[method] = function(url, data, config) {
    return this.request(
      mergeConfig(config || {}, {
        method: method,
        url: url,
        data: data,
      })
    )
  }
})

module.exports = Axios
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
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90

# dispatchRequest.js

Axios 实例对象的 request 方法中会调用这个函数
这个函数是实际发起请求的函数
兼容浏览器和 node 环境(浏览器的 XMLHttpRequest 和 node 的 http)

同时支持 node 环境 和浏览器环境

function throwIfCancellationRequested(config) {
  if (config.cancelToken) {
    config.cancelToken.throwIfRequested()
  }
}

module.exports = function dispatchRequest(config) {
  throwIfCancellationRequested(config) // 如果此时 已经取消了请求,直接抛出异常

  // Ensure headers exist
  config.headers = config.headers || {}

  // Transform request data
  config.data = transformData(
    config.data,
    config.headers,
    config.transformRequest // transformRequest 是一个数组,由用户自定义转换 data 数据的,并返回转换后的 data,可看👇👇👇
  )

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

  utils.forEach(
    ['delete', 'get', 'head', 'post', 'put', 'patch', 'common'],
    function cleanHeaderConfig(method) {
      delete config.headers[method]
    }
  )

  var adapter = config.adapter || defaults.adapter

  // 发起请求
  return adapter(config).then(
    function onAdapterResolution(response) {
      throwIfCancellationRequested(config)

      // Transform response data
      response.data = transformData(
        response.data,
        response.headers,
        config.transformResponse // transformResponse 是一个数组,用户自定义的响应数据转换,看👆👆👆
      )

      return response // 成功的响应
    },
    function onAdapterRejection(reason) {
      if (!isCancel(reason)) {
        throwIfCancellationRequested(config) // 如果此时已经取消了请求,直接抛出异常

        // Transform response data
        if (reason && reason.response) {
          reason.response.data = transformData(
            reason.response.data,
            reason.response.headers,
            config.transformResponse // transformResponse 是一个数组,用户自定义的响应数据转换,看👆👆👆
          )
        }
      }

      return Promise.reject(reason)
    }
  )
}
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
56
57
58
59
60
61
62
63
64
65
66
67

# cancel

取消请求的操作
实际上是调用 http 的 aborted(XMLHttpRequest 的 abort)方法

# Cancel.js

// 在取消操作时抛出的对象
function Cancel(message) {
  this.message = message
}

Cancel.prototype.toString = function toString() {
  return 'Cancel' + (this.message ? ': ' + this.message : '')
}

Cancel.prototype.__CANCEL__ = true // 用来判断是否加了 取消请求的 配置

module.exports = Cancel
1
2
3
4
5
6
7
8
9
10
11
12

# CancelToken.js

通过 CancelToken 创建对象,并传入 config 配置中
回调函数中会向外暴露一个可以控制 Promise 状态的函数

// CancelToken 构造函数,用于请求取消操作
function CancelToken(executor) {
  if (typeof executor !== 'function') {
    throw new TypeError('executor must be a function.')
  }

  var resolvePromise // 存储控制 Promse 状态为成功 的函数, 看👇👇👇
  this.promise = new Promise(function promiseExecutor(resolve) {
    resolvePromise = resolve
  })

  var token = this
  executor(function cancel(message) {
    // 这个函数是新建对象时作为参数传递出去的
    if (token.reason) {
      // 已取消直接 return
      return
    }

    token.reason = new Cancel(message) // 创建取消操作时抛出的对象
    resolvePromise(token.reason) // 改变 Promse 状态为成功, 看👆👆👆
  })
}

CancelToken.prototype.throwIfRequested = function throwIfRequested() {
  // 如果已请求取消,则抛出
  if (this.reason) {
    throw this.reason
  }
}

// 返回一个对象,该对象包含一个新的 CancelToken 和一个函数,调用该函数时 取消请求
CancelToken.source = function source() {
  var cancel
  var token = new CancelToken(function executor(c) {
    cancel = c
  })
  return {
    token: token,
    cancel: cancel,
  }
}

module.exports = CancelToken
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

# isCancel.js

仅判断是否是 Cancel 实例对象

module.exports = function isCancel(value) {
  return !!(value && value.__CANCEL__)
}
1
2
3

# adapters

node 的 http 模块封装的 ajax 和 XMLHttpRequest 封装的 ajax

可参考封装自己的 Ajax 操作

# xhr.js

module.exports = function xhrAdapter(config) {
  return new Promise(function dispatchXhrRequest(resolve, reject) {
    // 返回 Promise
    var requestData = config.data
    var requestHeaders = config.headers

    if (utils.isFormData(requestData)) {
      // 是 FormData 数据
      delete requestHeaders['Content-Type']
    }

    if (
      (utils.isBlob(requestData) || utils.isFile(requestData)) &&
      requestData.type
    ) {
      // 是 二进制、文件 数据
      delete requestHeaders['Content-Type']
    }

    var request = new XMLHttpRequest() // 创建 XMLHttpRequest 对象,可参考 "XMLHttpRequest 简单使用" 里介绍的 封装 Ajax

    if (config.auth) {
      // Authorization 处理
      var username = config.auth.username || ''
      var password = config.auth.password
        ? unescape(encodeURIComponent(config.auth.password)) // 对特殊字符进行转义
        : ''
      requestHeaders.Authorization = 'Basic ' + btoa(username + ':' + password) // 转换为 base64
    }

    var fullPath = buildFullPath(config.baseURL, config.url) // 完整请求地址

    // 初始化一个请求
    request.open(
      config.method.toUpperCase(),
      buildURL(fullPath, config.params, config.paramsSerializer), // 一定规则拼接 params 到路径上,paramsSerializer 用户自定义的 参数序列化函数
      true
    )

    request.timeout = config.timeout // 超时时间

    request.onreadystatechange = function handleLoad() {
      if (!request || request.readyState !== 4) {
        return
      }
      // 如果出错,return
      if (
        request.status === 0 &&
        !(request.responseURL && request.responseURL.indexOf('file:') === 0)
      ) {
        return
      }
      // 响应数据
      var responseHeaders =
        'getAllResponseHeaders' in request
          ? parseHeaders(request.getAllResponseHeaders())
          : null
      var responseData =
        !config.responseType || config.responseType === 'text'
          ? request.responseText
          : request.response
      var response = {
        data: responseData,
        status: request.status,
        statusText: request.statusText,
        headers: responseHeaders,
        config: config,
        request: request,
      }

      settle(resolve, reject, response) // 验证响应状态码默认 200 - 300, 根据状态码判断是 resolve 还是 reject。 用户可自定义合法值验证(config.validateStatus函数)

      request = null // 清空当前请求
    }
    // 当 request 被停止时触发(浏览器行为,非手动取消行为)
    request.onabort = function handleAbort() {
      if (!request) {
        return
      }

      reject(createError('Request aborted', config, 'ECONNABORTED', request))

      request = null
    }

    // 当 request 遭遇错误时触发
    request.onerror = function handleError() {
      reject(createError('Network Error', config, null, request))

      request = null
    }

    // 在预设时间内没有接收到响应时触发
    request.ontimeout = function handleTimeout() {
      var timeoutErrorMessage = 'timeout of ' + config.timeout + 'ms exceeded'
      if (config.timeoutErrorMessage) {
        timeoutErrorMessage = config.timeoutErrorMessage
      }
      reject(createError(timeoutErrorMessage, config, 'ECONNABORTED', request))

      request = null
    }

    if (utils.isStandardBrowserEnv()) {
      // 标准浏览器环境
      var xsrfValue =
        (config.withCredentials || isURLSameOrigin(fullPath)) &&
        config.xsrfCookieName
          ? cookies.read(config.xsrfCookieName)
          : undefined

      if (xsrfValue) {
        requestHeaders[config.xsrfHeaderName] = xsrfValue
      }
    }

    // request 添加 headers
    if ('setRequestHeader' in request) {
      utils.forEach(requestHeaders, function setRequestHeader(val, key) {
        if (
          typeof requestData === 'undefined' &&
          key.toLowerCase() === 'content-type'
        ) {
          // content-type 是 undefined 删除
          delete requestHeaders[key]
        } else {
          request.setRequestHeader(key, val)
        }
      })
    }

    // 用来指定跨域 Access-Control 请求是否应当带有授权信息,如 cookie 或授权 header 头。
    if (!utils.isUndefined(config.withCredentials)) {
      request.withCredentials = !!config.withCredentials
    }

    // 给 request 添加 responseType
    if (config.responseType) {
      try {
        request.responseType = config.responseType
      } catch (e) {
        if (config.responseType !== 'json') {
          throw e
        }
      }
    }

    // 下载进度 事件
    if (typeof config.onDownloadProgress === 'function') {
      request.addEventListener('progress', config.onDownloadProgress)
    }

    // 上传进度 事件
    if (typeof config.onUploadProgress === 'function' && request.upload) {
      request.upload.addEventListener('progress', config.onUploadProgress)
    }

    if (config.cancelToken) {
      // 取消请求
      config.cancelToken.promise.then(function onCanceled(cancel) {
        if (!request) {
          return
        }

        request.abort() // 真实调用 request 取消请求操作
        reject(cancel)
        request = null
      })
    }

    if (!requestData) {
      requestData = null
    }

    // 发送请求
    request.send(requestData)
  })
}
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
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178

# http.js

module.exports = function httpAdapter(config) {
  return new Promise(function dispatchHttpRequest(
    resolvePromise,
    rejectPromise
  ) {
    var resolve = function resolve(value) {
      resolvePromise(value)
    }
    var reject = function reject(value) {
      rejectPromise(value)
    }
    var data = config.data
    var headers = config.headers

    // 设置用户代理
    if (!headers['User-Agent'] && !headers['user-agent']) {
      headers['User-Agent'] = 'axios/' + pkg.version // axios 版本
    }
    // data 是对象或函数,转换为 流的形式传输
    if (data && !utils.isStream(data)) {
      if (Buffer.isBuffer(data)) {
      } else if (utils.isArrayBuffer(data)) {
        data = Buffer.from(new Uint8Array(data))
      } else if (utils.isString(data)) {
        data = Buffer.from(data, 'utf-8')
      } else {
        return reject(
          createError(
            'Data after transformation must be a string, an ArrayBuffer, a Buffer, or a Stream',
            config
          )
        )
      }
      // 添加 Content-Length 属性
      headers['Content-Length'] = data.length
    }

    // Http的安全认证机制
    var auth = undefined
    if (config.auth) {
      var username = config.auth.username || ''
      var password = config.auth.password || ''
      auth = username + ':' + password
    }

    // 解析url
    var fullPath = buildFullPath(config.baseURL, config.url)
    var parsed = url.parse(fullPath) // 将一个完整的URL地址,分为很多部分,常用的有:host、port、pathname、path、query
    var protocol = parsed.protocol || 'http:'

    if (!auth && parsed.auth) {
      var urlAuth = parsed.auth.split(':')
      var urlUsername = urlAuth[0] || ''
      var urlPassword = urlAuth[1] || ''
      auth = urlUsername + ':' + urlPassword
    }

    if (auth) {
      delete headers.Authorization
    }

    var isHttpsRequest = isHttps.test(protocol) // 是发起 https 请求
    var agent = isHttpsRequest ? config.httpsAgent : config.httpAgent

    var options = {
      // http.request 参数
      path: buildURL(
        parsed.path,
        config.params,
        config.paramsSerializer
      ).replace(/^\?/, ''),
      method: config.method.toUpperCase(),
      headers: headers,
      agent: agent,
      agents: { http: config.httpAgent, https: config.httpsAgent },
      auth: auth,
    }

    if (config.socketPath) {
      // Unix 域套接字
      options.socketPath = config.socketPath
    } else {
      options.hostname = parsed.hostname
      options.port = parsed.port
    }

    var proxy = config.proxy // 设置了代理
    if (!proxy && proxy !== false) {
      // 如果config 没有设置代理,从 当前运行环境中获取代理
      var proxyEnv = protocol.slice(0, -1) + '_proxy'
      var proxyUrl =
        process.env[proxyEnv] || process.env[proxyEnv.toUpperCase()] // 运行环境中的代理地址
      if (proxyUrl) {
        var parsedProxyUrl = url.parse(proxyUrl)
        var noProxyEnv = process.env.no_proxy || process.env.NO_PROXY
        var shouldProxy = true

        if (noProxyEnv) {
          var noProxy = noProxyEnv.split(',').map(function trim(s) {
            return s.trim()
          })

          shouldProxy = !noProxy.some(function proxyMatch(proxyElement) {
            if (!proxyElement) {
              return false
            }
            if (proxyElement === '*') {
              return true
            }
            if (
              proxyElement[0] === '.' &&
              parsed.hostname.substr(
                parsed.hostname.length - proxyElement.length
              ) === proxyElement
            ) {
              return true
            }

            return parsed.hostname === proxyElement
          })
        }

        if (shouldProxy) {
          proxy = {
            host: parsedProxyUrl.hostname,
            port: parsedProxyUrl.port,
          }

          if (parsedProxyUrl.auth) {
            var proxyUrlAuth = parsedProxyUrl.auth.split(':')
            proxy.auth = {
              username: proxyUrlAuth[0],
              password: proxyUrlAuth[1],
            }
          }
        }
      }
    }

    if (proxy) {
      options.hostname = proxy.host
      options.host = proxy.host
      options.headers.host =
        parsed.hostname + (parsed.port ? ':' + parsed.port : '')
      options.port = proxy.port
      options.path =
        protocol +
        '//' +
        parsed.hostname +
        (parsed.port ? ':' + parsed.port : '') +
        options.path

      // 代理安全认证
      if (proxy.auth) {
        var base64 = Buffer.from(
          proxy.auth.username + ':' + proxy.auth.password,
          'utf8'
        ).toString('base64')
        options.headers['Proxy-Authorization'] = 'Basic ' + base64
      }
    }

    var transport // 通过判断采用何种 node http(https|follow-redirects) 模块
    var isHttpsProxy =
      isHttpsRequest && (proxy ? isHttps.test(proxy.protocol) : true)
    if (config.transport) {
      transport = config.transport // 优先使用 用户配置的 传输模块
    } else if (config.maxRedirects === 0) {
      transport = isHttpsProxy ? https : http // http(https)
    } else {
      if (config.maxRedirects) {
        options.maxRedirects = config.maxRedirects
      }
      transport = isHttpsProxy ? httpsFollow : httpFollow // follow-redirects 重定向
    }

    if (config.maxBodyLength > -1) {
      options.maxBodyLength = config.maxBodyLength
    }

    // 创建请求
    var req = transport.request(options, function handleResponse(res) {
      if (req.aborted) return // 请求已取消 return

      // 压缩响应主体
      var stream = res

      // 在重定向的情况下返回最后一个请求
      var lastRequest = res.req || req

      // 如果没有内容,HEAD请求(或config配置了) 不应该解压
      if (
        res.statusCode !== 204 &&
        lastRequest.method !== 'HEAD' &&
        config.decompress !== false
      ) {
        switch (res.headers['content-encoding']) {
          case 'gzip':
          case 'compress':
          case 'deflate':
            // 将 unzipper 添加到 body 流处理管道
            stream = stream.pipe(zlib.createUnzip()) // 将流数据解压缩
            // 删除内容编码
            delete res.headers['content-encoding']
            break
        }
      }

      var response = {
        // 构建响应对象
        status: res.statusCode,
        statusText: res.statusMessage,
        headers: res.headers,
        config: config,
        request: lastRequest,
      }

      if (config.responseType === 'stream') {
        // 如果要求响应类型为 流,直接返回 流
        response.data = stream
        settle(resolve, reject, response) // 验证响应状态码默认 200 - 300, 根据状态码判断是 resolve 还是 reject。 用户可自定义合法值验证(config.validateStatus函数)
      } else {
        var responseBuffer = []
        stream.on('data', function handleStreamData(chunk) {
          responseBuffer.push(chunk)

          // 如果设置了 maxContentLength,在不符合 maxContentLength 的情况下,返回失败结果并关闭 读取流
          if (
            config.maxContentLength > -1 &&
            Buffer.concat(responseBuffer).length > config.maxContentLength
          ) {
            stream.destroy()
            reject(
              createError(
                'maxContentLength size of ' +
                  config.maxContentLength +
                  ' exceeded',
                config,
                null,
                lastRequest
              )
            )
          }
        })
        // 由于底层内部的故障而无法生成数据,或者流的实现尝试推送无效的数据块时触发
        stream.on('error', function handleStreamError(err) {
          if (req.aborted) return
          reject(enhanceError(err, config, null, lastRequest))
        })
        // 流 读取完毕时触发
        stream.on('end', function handleStreamEnd() {
          var responseData = Buffer.concat(responseBuffer) // 将接收的数据 合并成一个 buffer
          if (config.responseType !== 'arraybuffer') {
            // 非 arraybuffer
            responseData = responseData.toString(config.responseEncoding) // 转换成用户配置的 responseEncoding(2-36) 进制
            if (
              !config.responseEncoding ||
              config.responseEncoding === 'utf8'
            ) {
              responseData = utils.stripBOM(responseData)
            }
          }

          response.data = responseData
          settle(resolve, reject, response)
        })
      }
    })

    // 请求遇到问题
    req.on('error', function handleRequestError(err) {
      if (req.aborted && err.code !== 'ERR_FR_TOO_MANY_REDIRECTS') return
      reject(enhanceError(err, config, null, req))
    })

    if (config.timeout) {
      // 一旦将套接字分配给此请求并且连接了套接字,就会调用 socket.setTimeout()
      // config.timeout: 请求超时前的毫秒数, handleRequestTimeout: 发生超时时要调用的可选函数。相当于绑定到 'timeout' 事件
      req.setTimeout(config.timeout, function handleRequestTimeout() {
        req.abort()
        reject(
          createError(
            'timeout of ' + config.timeout + 'ms exceeded',
            config,
            'ECONNABORTED',
            req
          )
        )
      })
    }

    if (config.cancelToken) {
      // 取消处理
      config.cancelToken.promise.then(function onCanceled(cancel) {
        if (req.aborted) return

        req.abort()
        reject(cancel)
      })
    }

    // 发送请求
    if (utils.isStream(data)) {
      data
        .on('error', function handleStreamError(err) {
          reject(enhanceError(err, config, null, req))
        })
        .pipe(req)
    } else {
      req.end(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
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
上次更新: 2021/08/13, 10:20:29
分析Promise实现
vueRouter源码分析

← 分析Promise实现 vueRouter源码分析→

最近更新
01
代码片段
04-22
02
koa全家桶
03-29
03
mocks项目复盘
03-29
更多文章>
Theme by Vdoing | Copyright © 2021-2022 coderly | MIT License
  • 跟随系统
  • 浅色模式
  • 深色模式
  • 阅读模式