axios 源码深入分析之 XHR 和 axios 的理解和使用

一、XHR 的相关内容

  1. MDN 文档 :https://developer.mozilla.org/zh-CN/docs/Web/API/XMLHttpRequest
  2. 理解 XHR,如下所示:
  • 使用 XMLHttpRequest (XHR) 对象可以与服务器交互, 也就是发送 ajax 请求
  • 前端可以获取到数据,而无需让整个的页面刷新。
  • 这使得 Web 页面可以只更新页面的局部,而不影响用户的操作。
  1. 区别 ajax 请求与一般 HTTP 请求,如下所示:
  • ajax 请求是一种特别的 http 请求:
  • 只有通过 XHR/fetch 发送的是 ajax 请求, 其它都是一般 HTTP 请求
  • 对服务器端来说, 没有任何区别, 区别在浏览器端
    浏览器端发请求:
  • 只有 XHRfetch 发出的才是 ajax 请求, 其它所有的都是非 ajax 请求
  • 浏览器端接收到响应,如下:
    • 一般请求: 浏览器一般会直接显示响应体数据, 也就是我们常说的刷新/跳转页面
    • ajax 请求: 浏览器不会对界面进行任何更新操作, 只是调用监视的回调函数并传入响应相关数据
  1. API,如下所示:
  • XMLHttpRequest(): 创建 XHR 对象的构造函数
  • status: 响应状态码值, 比如 200, 404
  • statusText: 响应状态文本
  • readyState: 标识请求状态的只读属性
    • 0: 初始
    • 1: open()之后
    • 2: send()之后
    • 3: 请求中
    • 4: 请求完成
  • onreadystatechange: 绑定 readyState 改变的监听
  • responseType: 指定响应数据类型, 如果是 'json' , 得到响应后自动解析响应体数据
  • response: 响应体数据, 类型取决于 responseType 的指定
  • timeout: 指定请求超时时间, 默认为 0 代表没有限制
  • ontimeout: 绑定超时的监听
  • onerror: 绑定请求网络错误的监听
  • open(): 初始化一个请求, 参数为: (method, url[, async])
  • send(data): 发送请求
  • abort(): 中断请求
  • getResponseHeader(name): 获取指定名称的响应头值
  • getAllResponseHeaders(): 获取所有响应头组成的字符串
  • setRequestHeader(name, value): 设置请求头
  1. 使用 XHR 封装一个发 ajax 请求的通用函数,如下所示:
  • 函数的返回值为 promise, 成功的结果为 response, 异常的结果为 error
  • 能处理多种类型的请求: GET/POST/PUT/DELETE
  • 函数的参数为一个配置对象,url/method/params/data,如下:
    {
          
           
      url: '', // 请求地址 
      method: '', // 请求方式 GET/POST/PUT/DELETE 
      params: {
          
          }, // GET/DELETE 请求的 query 参数 
      data: {
          
          }, // POST 或 DELETE 请求的请求体参数
    }
    
  • 响应 json 数据自动解析为了 js
  1. 使用 XHR 封装一个发 ajax 请求的通用函数,代码如下所示:
<!DOCTYPE html>
<html lang="en">
<head>
  <meta charset="UTF-8">
  <meta name="viewport" content="width=device-width, initial-scale=1.0">
  <meta http-equiv="X-UA-Compatible" content="ie=edge">
  <title>使用XHR封装ajax请求参数</title>
</head>
<body>
  <button onclick="testGet()">发送GET请求</button><br>
  <button onclick="testPost()">发送POST请求</button><br>
  <button onclick="testPut()">发送PUT请求</button><br>
  <button onclick="testDelete()">发送Delete请求</button><br>


  <script>

    /* 1. GET请求: 从服务器端获取数据*/
    function testGet() {
     
     
      axios({
     
     
        // url: 'http://localhost:3000/posts',
        url: 'http://localhost:3000/posts2',
        method: 'GET',
        params: {
     
     
          id: 1,
          xxx: 'abc'
        }
      }).then(
        response => {
     
     
          console.log(response)
        },
        error => {
     
     
          alert(error.message)
        }
      )
    }

    /* 2. POST请求: 服务器端保存数据*/
    function testPost() {
     
     
      axios({
     
     
        url: 'http://localhost:3000/posts',
        method: 'POST',
        data: {
     
     
          "title": "json-server---", 
          "author": "typicode---"
        }
      }).then(
        response => {
     
     
          console.log(response)
        },
        error => {
     
     
          alert(error.message)
        }
      )
    }

    /* 3. PUT请求: 服务器端更新数据*/
    function testPut() {
     
     
      axios({
     
     
        url: 'http://localhost:3000/posts/1',
        method: 'put',
        data: {
     
     
          "title": "json-server+++", 
          "author": "typicode+++"
        }
      }).then(
        response => {
     
     
          console.log(response)
        },
        error => {
     
     
          alert(error.message)
        }
      )
    }

    /* 2. DELETE请求: 服务器端删除数据*/
    function testDelete() {
     
     
      axios({
     
     
        url: 'http://localhost:3000/posts/2',
        method: 'delete'
      }).then(
        response => {
     
     
          console.log(response)
        },
        error => {
     
     
          alert(error.message)
        }
      )
    }

    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.keys(params).forEach(key => {
     
     
          queryString += `${
       
       key}=${
       
       params[key]}&`
        })
        if (queryString) {
     
      // id=1&xxx=abc&
          // 去除最后的&
          queryString = queryString.substring(0, queryString.length-1)
          // 接到url
          url += '?' + queryString
        }


        // 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))
          }
        }
      })
    }

  </script>
</body>
</html>

二、axios 的理解和使用

  1. axios 是什么,如下所示:
  • 前端最流行的 ajax 请求库
  • react/vue 官方都推荐使用 axiosajax 请求
  • 文档: https://github.com/axios/axios
  1. axios 的特点,如下所示:
  • 基于 promise 的封装 XHR 的异步 ajax 请求库
  • 浏览器端/ node 端都可以使用
  • 支持请求/响应拦截器
  • 支持请求取消
  • 请求/响应数据转换
  • 批量发送多个请求
  1. axios 常用语法,如下所示:
  • axios(config): 通用/最本质的发任意类型请求的方式
  • axios(url[, config]): 可以只指定 urlget 请求
  • 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.spread(): 用来指定接收所有成功数据的回调函数的方法
  1. axios 的基本使用,代码如下所示:
<!DOCTYPE html>
<html lang="en">
<head>
  <meta charset="UTF-8">
  <meta name="viewport" content="width=device-width, initial-scale=1.0">
  <meta http-equiv="X-UA-Compatible" content="ie=edge">
  <title>axios基本使用</title>
</head>
<body>
  
    <div>
      <button onclick="testGet()">GET请求</button>
      <button onclick="testPost()">POST请求</button>
      <button onclick="testPut()">PUT请求</button>
      <button onclick="testDelete()">DELETE请求</button>
    </div>
  
    <script src="https://cdn.bootcss.com/axios/0.19.0/axios.js"></script>
    <script>
      // 指定默认配置
      axios.defaults.baseURL = 'http://localhost:3000'

      /* 1. GET请求: 从服务器端获取数据*/
      function testGet() {
     
     
        // axios.get('/posts?id=1')
        axios({
     
     
          url: '/posts',
          params: {
     
     
            id: 1
          }
        })
          .then(response => {
     
     
            console.log('/posts get', response.data)
          })
      }
  
      /* 2. POST请求: 向服务器端添加新数据*/
      function testPost() {
     
     
        // axios.post('/posts', {"title": "json-server3", "author": "typicode3"})
        axios({
     
     
          url: '/posts',
          method: 'post',
          data: {
     
     "title": "json-server4", "author": "typicode4"}
        })
          .then(response => {
     
     
            console.log('/posts post', response.data)
          })
      }
  
      /* 3. PUT请求: 更新服务器端已经数据 */
      function testPut() {
     
     
        // axios.put('http://localhost:3000/posts/4', {"title": "json-server...", "author": "typicode..."})
        axios({
     
     
          url: '/posts/4',
          method: 'put',
          data: {
     
     "title": "json-server5", "author": "typicode5"}
        })
          .then(response => {
     
     
            console.log('/posts put', response.data)
          })
      }
  
      /* 4. DELETE请求: 删除服务器端数据 */
      function testDelete() {
     
     
        // axios.delete('http://localhost:3000/posts/4')
        axios({
     
     
          url: '/posts/5',
          method: 'delete'
        })
          .then(response => {
     
     
            console.log('/posts delete', response.data)
          })
      }
  
    </script>
</body>
</html>
  1. axios.create(config),如下:
  • 根据指定配置创建一个新的 axios, 也就就每个新 axios 都有自己的配置
  • axios 只是没有取消请求和批量发请求的方法, 其它所有语法都是一致的
  • 为什么要设计这个语法? 如下所示:
    • 需求: 项目中有部分接口需要的配置与另一部分接口需要的配置不太一样, 如何处理
    • 解决: 创建两个新 axios, 每个都有自己特有的配置, 分别应用到不同要 求的接口请求中
  1. axios.create(),代码如下所示:
<!DOCTYPE html>
<html lang="en">
<head>
  <meta charset="UTF-8">
  <meta name="viewport" content="width=device-width, initial-scale=1.0">
  <meta http-equiv="X-UA-Compatible" content="ie=edge">
  <title>axios.create()</title>
</head>
<body>
  <script src="https://cdn.bootcss.com/axios/0.19.0/axios.js"></script>
  <script>
    axios.defaults.baseURL = 'http://localhost:3000'

    // 使用axios发请求
    axios({
     
     
      url: '/posts' // 请求3000
    })

    // axios({
     
     
    //   url: '/xxx' // 请求4000
    // })

    const instance = axios.create({
     
     
      baseURL: 'http://localhost:4000'
    })

    // 使用instance发请求
    // instance({
     
     
    //   url: '/xxx'  // 请求4000
    // })
    instance.get('/xxx')
  </script>
</body>
</html>
  1. 拦截器函数/ ajax 请求/请求的回调函数的调用顺序,如下所示:
  • 说明: 调用 axios() 并不是立即发送 ajax 请求, 而是需要经历一个较长的流程
  • 流程: 请求拦截器2 => 请求拦截器 1 => 发ajax请求 => 响应拦截器1 => 响 应拦截器 2 => 请求的回调
  • 注意: 此流程是通过 promise 串连起来的, 请求拦截器传递的是 config, 响应 拦截器传递的是 response
  1. axios 的处理链流程,代码如下所示:
<!DOCTYPE html>
<html lang="en">
<head>
  <meta charset="UTF-8">
  <meta name="viewport" content="width=device-width, initial-scale=1.0">
  <meta http-equiv="X-UA-Compatible" content="ie=edge">
  <title>axios的处理链流程</title>
</head>
<body>
  
  <script src="https://cdn.bootcss.com/axios/0.19.0/axios.js"></script>
  <script>

    // 添加请求拦截器(回调函数)
    axios.interceptors.request.use(
      config => {
     
     
        console.log('request interceptor1 onResolved()') // 2 后
        return config
      },
      error => {
     
     
        console.log('request interceptor1 onRejected()')
        return Promise.reject(error);
      }
    )
    axios.interceptors.request.use(
      config => {
     
     
        console.log('request interceptor2 onResolved()') // 1 先
        return config
      },
      error => {
     
     
        console.log('request interceptor2 onRejected()')
        return Promise.reject(error);
      }
    )
    // 添加响应拦截器
    axios.interceptors.response.use(
      response => {
     
     
        console.log('response interceptor1 onResolved()') // 3 先
        return response
      },
      function (error) {
     
     
        console.log('response interceptor1 onRejected()')
        return Promise.reject(error);
      }
    )
    axios.interceptors.response.use(
      response => {
     
     
        console.log('response interceptor2 onResolved()') // 4 后
        return response
      },
      function (error) {
     
     
        console.log('response interceptor2 onRejected()')
        return Promise.reject(error);
      }
    )

    axios.get('http://localhost:3000/posts')
      .then(response => {
     
     
        console.log('data', response.data)
      })
      .catch(error => {
     
     
        console.log('error', error.message)
      })

    /* axios.get('http://localhost:/posts2')
      .then(response => {
        console.log('data', response.data)
      })
      .catch(error => {
        console.log('error', error.message)
      }) 
    */
  </script>
</body>
</html>

最后的处理结果,request interceptor2 是第一个请求,request interceptor1 是第二个请求,response interceptor1 是第一个响应,response interceptor2 是第二个响应。

  1. 取消请求,如下所示:
  • 基本流程,如下:
    • 配置 cancelToken 对象
    • 缓存用于取消请求的 cancel 函数
    • 在后面特定时机调用 cancel 函数取消请求
    • 在错误回调中判断如果 errorcancel, 做相应处理
  • 实现功能,如下:
    • 点击按钮, 取消某个正在请求中的请求 在请求一个接口前,
    • 取消前面一个未完成的请求
  1. 对于 axios 取消请求的,代码如下所示:
  • 第一个:
    <!DOCTYPE html>
    <html lang="en">
    <head>
      <meta charset="UTF-8">
      <meta name="viewport" content="width=device-width, initial-scale=1.0">
      <meta http-equiv="X-UA-Compatible" content="ie=edge">
      <title>取消请求</title>
    </head>
    <body>
      <button onclick="getProducts1()">获取商品列表1</button><br>
      <button onclick="getProducts2()">获取商品列表2</button><br>
      <button onclick="cancelReq()">取消请求</button><br>
    
      <script src="https://cdn.bootcss.com/axios/0.19.0/axios.js"></script>
      <script>
        let cancel  // 用于保存取消请求的函数
        function getProducts1() {
           
           
          axios({
           
           
            url: 'http://localhost:4000/products1',
            cancelToken: new axios.CancelToken((c) => {
           
            // c是用于取消当前请求的函数
              // 保存取消函数, 用于之后可能需要取消当前请求
              cancel = c
            })
          }).then(
            response => {
           
           
              cancel = null
              console.log('请求1成功了', response.data)
            },
            error => {
           
           
              cancel = null
              console.log('请求1失败了', error.message, error)
            }
          )
        }
    
        function getProducts2() {
           
           
          axios({
           
           
            url: 'http://localhost:4000/products2'
          }).then(
            response => {
           
           
              console.log('请求2成功了', response.data)
            },
            error => {
           
           
              cancel = null
              console.log('请求2失败了', error.message)
            }
          )
        }
    
        function cancelReq() {
           
           
          // alert('取消请求')
          // 执行取消请求的函数
          if (typeof cancel === 'function') {
           
           
            cancel('强制取消请求')
          } else {
           
           
            console.log('没有可取消的请求')
          }
        }
      </script>
    </body>
    </html>
    

猜你喜欢

转载自blog.csdn.net/weixin_42614080/article/details/107008757
今日推荐