Tian Jiale's Blog

axios 入门和源码分析

http 介绍

概述

HTTP 是一种能够获取如 HTML 这样的网络资源的 protocol(通讯协议)。它是在 Web 上进行数据交换的基础,是一种 client-server 协议,也就是说,请求通常是由像浏览器这样的接受方发起的。一个完整的 Web 文档通常是由不同的子文档拼接而成的,像是文本、布局描述、图片、视频、脚本等等。

Fetching_a_page

客户端和服务端通过交换各自的消息(与数据流正好相反)进行交互。由像浏览器这样的客户端发出的消息叫做 requests,被服务端响应的消息叫做 responses。

HTTP & layers

HTTP 被设计于 20 世纪 90 年代初期,是一种可扩展的协议。它是应用层的协议,通过TCP,或者是TLS-加密的 TCP 连接来发送,理论上任何可靠的传输协议都可以使用。因为其良好的扩展性,时至今日,它不仅被用来传输超文本文档,还用来传输图片、视频或者向服务器发送如 HTML 表单这样的信息。HTTP 还可以根据网页需求,仅获取部分 Web 文档内容更新网页。

详细内容见 HTTP 概述

HTTP 请求方法

HTTP 定义了一组请求方法, 以表明要对给定资源执行的操作。指示针对给定资源要执行的期望动作. 虽然他们也可以是名词, 但这些请求方法有时被称为 HTTP 动词. 每一个请求方法都实现了不同的语义, 但一些共同的特征由一组共享:: 例如一个请求方法可以是 safe, idempotent, 或 cacheable.

HTTP 请求头

HTTP 消息头允许客户端和服务器通过 requestresponse传递附加信息。一个请求头由名称(不区分大小写)后跟一个冒号“:”,冒号后跟具体的值(不带换行符)组成。该值前面的引导空白会被忽略。

常用请求头:AcceptAccess-Control-Allow-OriginConnectionContent-LengthContent-TypeCookieHostOriginUser-Agent

HTTP 请求体

http 请求体通常用于 POST 请求中,包括但不限于提交表单等操作。

格式为在请求头之后空一行填写,例如:

POST /test HTTP/1.1
Host: foo.example
Content-Type: application/x-www-form-urlencoded
Content-Length: 27

field1=value1&field2=value2

HTTP 响应代码

HTTP 响应状态代码指示特定 HTTP 请求是否已成功完成。响应分为五类:信息响应(100199),成功响应(200299),重定向(300399),客户端错误(400499)和服务器错误 (500599)。状态代码由 section 10 of RFC 2616定义。

详见:HTTP 响应代码

XHR 的理解和使用

MDN 文档

链接

理解

  1. 使用 XMLHttpRequest (XHR)对象可以与服务器交互, 也就是发送 ajax 请求。
  2. 前端可以获取到数据,而无需让整个的页面刷新。
  3. 这使得 Web 页面可以只更新页面的局部,而不影响用户的操作。
  4. xhr 和 fetch 都是 JavaScript 的底层 API,互不相关。

区别一般 http 请求与 ajax 请求

  1. ajax 请求是一种特别的 http 请求。

  2. 对服务器端来说,没有任何区别,区别在浏览器端。

  3. 浏览器端发请求:只有 XHR 或 fetch 发出的才是 ajax 请求,其它所有的都是 非 ajax 请求。

  4. 浏览器端接收到响应

    (1) 一般请求:浏览器一般会直接显示响应体数据,也就是我们常说的刷新 / 跳转页面。

    (2) ajax 请求:浏览器不会对界面进行任何更新操作,只是调用监视的回调 函数并传入响应相关数据。

API

构造函数

  • XMLHttpRequest()

    该构造函数用于初始化一个 XMLHttpRequest 实例对象。在调用下列任何其他方法之前,必须先调用该构造函数,或通过其他方式,得到一个实例对象。

属性

此接口继承了 XMLHttpRequestEventTarget 和 EventTarget 的属性。

  • XMLHttpRequest.onreadystatechange

    当 readyState 属性发生变化时,调用的 EventHandler (en-US)。

  • XMLHttpRequest.readyState 只读

    返回 一个无符号短整型(unsigned short)数字,代表请求的状态码。

  • XMLHttpRequest.response 只读

    返回一个 ArrayBuffer、Blob、Document,或 DOMString,具体是哪种类型取决于 XMLHttpRequest.responseType 的值。其中包含整个响应实体(response entity body)。

  • XMLHttpRequest.responseText 只读

    返回一个 DOMString,该 DOMString 包含对请求的响应,如果请求未成功或尚未发送,则返回 null。

  • XMLHttpRequest.responseType

    一个用于定义响应类型的枚举值(enumerated value)。

  • XMLHttpRequest.responseURL 只读

    返回经过序列化(serialized)的响应 URL,如果该 URL 为空,则返回空字符串。

  • XMLHttpRequest.responseXML 只读

    返回一个 Document,其中包含该请求的响应,如果请求未成功、尚未发送或时不能被解析为 XML 或 HTML,则返回 null。

  • XMLHttpRequest.status 只读

    返回一个无符号短整型(unsigned short)数字,代表请求的响应状态。

  • XMLHttpRequest.statusText 只读

    返回一个 DOMString,其中包含 HTTP 服务器返回的响应状态。与 XMLHTTPRequest.status 不同的是,它包含完整的响应状态文本(例如,“200 OK”)。

    注意:根据 HTTP/2 规范(8.1.2.4 Response Pseudo-Header Fields,响应伪标头字段),HTTP/2 没有定义任何用于携带 HTTP/1.1 状态行中包含的版本(version)或者原因短语(reason phrase)的方法。

  • XMLHttpRequest.timeout

    一个无符号长整型(unsigned long)数字,表示该请求的最大请求时间(毫秒),若超出该时间,请求会自动终止。

  • XMLHttpRequestEventTarget.ontimeout

    当请求超时调用的 EventHandler。

  • XMLHttpRequest.upload 只读

    XMLHttpRequestUpload,代表上传进度。

  • XMLHttpRequest.withCredentials

    一个布尔值,用来指定跨域 Access-Control 请求是否应当带有授权信息,如 cookie 或授权 header 头。

非标准属性

  • 事件处理器

作为 XMLHttpRequest 实例的属性之一,所有浏览器都支持 onreadystatechange。

后来,许多浏览器实现了一些额外的事件(onload、onerror、onprogress 等)。

更多现代浏览器,包括 Firefox,除了可以设置 on* 属性外,也提供标准的监听器 addEventListener() API 来监听 XMLHttpRequest 事件。

方法

  • XMLHttpRequest.abort()

    如果请求已被发出,则立刻中止请求。

  • XMLHttpRequest.getAllResponseHeaders()

    以字符串的形式返回所有用 CRLF 分隔的响应头,如果没有收到响应,则返回 null。

  • XMLHttpRequest.getResponseHeader()

    返回包含指定响应头的字符串,如果响应尚未收到或响应中不存在该报头,则返回 null。

  • XMLHttpRequest.open()

    初始化一个请求。该方法只能在 JavaScript 代码中使用,若要在 native code 中初始化请求,请使用 openRequest()。

  • XMLHttpRequest.overrideMimeType()

    覆写由服务器返回的 MIME 类型。

  • XMLHttpRequest.send()

    发送请求。如果请求是异步的(默认),那么该方法将在请求发送后立即返回。

  • XMLHttpRequest.setRequestHeader() 设置 HTTP 请求头的值。必须在 open() 之后、send() 之前调用 setRequestHeader() 方法。

事件

  • abort

    当 request 被停止时触发,例如当程序调用 XMLHttpRequest.abort() 时。

  • error

    当 request 遭遇错误时触发。

  • load

    XMLHttpRequest 请求成功完成时触发。

  • loadend

    当请求结束时触发, 无论请求成功 ( load) 还是失败 (abort 或 error)。

  • loadstart

    接收到响应数据时触发。

  • progress

    当请求接收到更多数据时,周期性地触发。

  • timeout

    在预设时间内没有接收到响应时触发。

XHR 的 Ajax 封装(简单版 axios)

/**
 * 1.函数的返回值为promise, 成功的结果为response, 失败的结果为error
 * 2.能处理多种类型的请求: GET/POST/PUT/DELETE
 * 3.函数的参数为一个配置对象
 *   {
 *     url: '',   // 请求地址
 *     method: '',   // 请求方式GET/POST/PUT/DELETE
 *     params: {},  // GET/DELETE请求的query参数
 *     data: {}, // POST或DELETE请求的请求体参数
 *   }
 * 4.响应json数据自动解析为js的对象/数组
 **/
function axios({ url, method = 'GET', params = {}, data = {} }) {
  // 返回一个promise对象
  return new Promise((resolve, reject) => {
    // 处理method(转大写)
    method = method.toUpperCase();

    // 处理query参数(拼接到url上)   id=1&xxx=abc
    let queryString = '';
    Object.entries(params).forEach((item) => {
      queryString += `${item[0]}=${item[1]}&`;
    });
    // 拼接url
    url += queryString === '' ? queryString.substring(0, queryString.length - 1) : '';

    // 1. 执行异步ajax请求
    // 创建xhr对象
    const request = new XMLHttpRequest();
    // 打开连接(初始化请求, 没有发送请求)
    request.open(method, url, true);

    // 发送请求
    if (method === 'GET' || method === 'DELETE') {
      request.send();
    } else if (method === 'POST' || method === 'PUT') {
      request.setRequestHeader('Content-Type', 'application/json;charset=utf-8'); // 告诉服务器请求体的格式是json
      request.send(JSON.stringify(data)); // 发送json格式请求体参数
    }

    // 绑定状态改变的监听
    request.onreadystatechange = function () {
      // 如果请求没有完成, 直接结束
      if (request.readyState !== 4) {
        return;
      }
      // 如果响应状态码在[200, 300)之间代表成功, 否则失败
      const { status, statusText } = request;
      // 2.1. 如果请求成功了, 调用resolve()
      if (status >= 200 && status <= 299) {
        // 准备结果数据对象response
        const response = {
          data: JSON.parse(request.response),
          status,
          statusText,
        };
        resolve(response);
      } else {
        // 2.2. 如果请求失败了, 调用reject()
        reject(new Error('request error status is ' + status));
      }
    };
  });
}

axios 的理解和使用

axios 是什么

  1. 前端最流行的 ajax 请求库
  2. react/vue 官方都推荐使用 axios 发 ajax 请求
  3. 开源地址

.axios 特点

  1. 基本 promise 的异步 ajax 请求库
  2. 浏览器端/node 端都可以使用
  3. 支持请求/响应拦截器
  4. 支持请求取消
  5. 请求/响应数据转换
  6. 批量发送多个请求

axios 常用语法

axios(config): 通用/最本质的发任意类型请求的方式

axios(url[, config]): 可以只指定 url 发 get 请求

axios.request(config): 等同于 axios(config)

axios.get(url[, config]): 发 get 请求

axios.delete(url[, config]): 发 delete 请求

axios.post(url[, data, config]): 发 post 请求

axios.put(url[, data, config]): 发 put 请求

axios.defaults.xxx: 请求的默认全局配置

axios.interceptors.request.use(): 添加请求拦截器

axios.interceptors.response.use(): 添加响应拦截器

axios.create([config]): 创建一个新的 axios(它没有下面的功能)

axios.Cancel(): 用于创建取消请求的错误对象

axios.CancelToken(): 用于创建取消请求的 token 对象

axios.isCancel(): 是否是一个取消请求的错误

axios.all(promises): 用于批量执行多个异步请求

难点语法的理解和使用

axios.create(config)

  1. 根据指定配置创建一个新的 axios, 也就就每个新 axios 都有自己的配置

  2. 新 axios 只是没有取消请求和批量发请求的方法, 其它所有语法都是一致的

  3. 为什么要设计这个语法?

    (1) 需求: 项目中有部分接口需要的配置与另一部分接口需要的配置不太一 样, 如何处理

    (2) 解决: 创建 2 个新 axios, 每个都有自己特有的配置, 分别应用到不同要 求的接口请求中

拦截器函数/ajax 请求/请求的回调函数的调用顺序

  1. 说明: 调用 axios()并不是立即发送 ajax 请求, 而是需要经历一个较长的流程
  2. 流程: 请求拦截器 2 => 请求拦截器 1 => 发 ajax 请求 => 响应拦截器 1 => 响应拦截器 2 => 请求的回调
  3. 注意: 此流程是通过 promise 串连起来的, 请求拦截器传递的是 config, 响应拦截器传递的是 response

取消请求

  1. 基本流程

    配置 cancelToken 对象

    缓存用于取消请求的 cancel 函数

    在后面特定时机调用 cancel 函数取消请求

    在错误回调中判断如果 error 是 cancel, 做相应处理

  2. 实现功能

    点击按钮, 取消某个正在请求中的请求;在请求一个接口前, 取消前面一个未完成的请求

axios 源码分析

axios 与 Axios 的关系?

  1. 从语法上来说: axios 不是 Axios 的实例
  2. 从功能上来说: axios 是 Axios 的实例
  3. axios 是 Axios.prototype.request 函数 bind()返回的函数
  4. axios 作为对象有 Axios 原型对象上的所有方法, 有 Axios

instance 与 axios 的区别?

  1. 相同:

    (1) 都是一个能发任意请求的函数: request(config)

    (2) 都有发特定请求的各种方法: get()/post()/put()/delete()

    (3) 都有默认配置和拦截器的属性: defaults/interceptors

  2. 不同:

    (1) 默认匹配的值很可能不一样

    (2) instance 没有 axios 后面添加的一些方法: create()/CancelToken()/all()

axios 运行的整体流程

QQ截图20210417000728

  1. 整体流程:

    request(config) ==> dispatchRequest(config) ==> xhrAdapter(config)

  2. request(config):

    将请求拦截器 / dispatchRequest() / 响应拦截器 通过 promise 链串连起来, 返回 promise

  3. dispatchRequest(config):

    转换请求数据 ==> 调用 xhrAdapter()发请求 ==> 请求返回后转换响应数据。返回 promis

  4. xhrAdapter(config):

    创建 XHR 对象, 根据 config 进行相应设置, 发送特定请求, 并接收响应数据, 返回 promise

axios 的请求/响应拦截器是什么?

QQ截图20210417001018

  1. 请求拦截器:

    在真正发送请求前执行的回调函数

    可以对请求进行检查或配置进行特定处理

    成功的回调函数, 传递的默认是 config(也必须是)

    失败的回调函数, 传递的默认是 error

  2. 响应拦截器

    在请求得到响应后执行的回调函数

    可以对响应数据进行特定处理

    成功的回调函数, 传递的默认是 response

    失败的回调函数, 传递的默认是 erro

axios 的请求/响应数据转换器是什么?

  1. 请求转换器: 对请求头和请求体数据进行特定处理的函数

    if (utils.isObject(data)) {
      setContentTypeIfUnset(headers, 'application/json;charset=utf-8');
      return JSON.stringify(data);
    }
    
  2. 响应转换器: 将响应体 json 字符串解析为 js 对象或数组的函数

    response.data = JSON.parse(response.data);
    

response 的整体结构

{
  data,
  status,
  statusText,
  headers,
  config,
  request,
}

error 的整体结构

{
  message,
  response,
  request,
}

如何取消未完成的请求?

  1. 当配置了 cancelToken 对象时, 保存 cancel 函数

    (1) 创建一个用于将来中断请求的 Promise 对象: cancelPromise

    (2) 并定义了一个用于取消请求的 resolved 函数: cancel

    (3) 将 cancel 函数传递出来

  2. 调用 cancel()取消请求

    (1) 执行 cacel 函数, 传入错误信息 message

    (2) 内部会让 cancelPromise 变为成功, 且成功的值为一个 Cancel 对象

    (3) 在 cancelPromise 的成功回调中中断请求, 并让发请求的 proimse 失败, 失败的 reason 为 Cacel 对象