【axios】XHR的ajax封裝+axios攔截器呼叫+請求取消

羽飛蕭瑟起發表於2020-10-15

Http

1. 前後臺互動的基本過程

1. 前後應用從瀏覽器端向伺服器傳送HTTP請求(請求報文)
2. 後臺伺服器接收到請求後, 排程伺服器應用處理請求, 向瀏覽器端返回HTTP響應(響應報文)
3. 瀏覽器端接收到響應, 解析顯示響應體/呼叫監視回撥

2. HTTP請求報文

1. 請求行: 請求方式/url
    method url
    GET /product_detail?id=2
    POST /login
2. 多個請求頭: 一個請求頭由name:value組成, 如Host/Cookie/Content-Type頭
    Host: www.baidu.com
    Cookie: BAIDUID=AD3B0FA706E; BIDUPSID=AD3B0FA706;
    Content-Type: application/x-www-form-urlencoded 或者 application/json
3. 請求體
    username=tom&pwd=123
    {"username": "tom", "pwd": 123}

3. HTTP響應報文

1. 響應行: 響應狀態碼/對應的文字
2. 多個響應頭: 如 Content-Type / Set-Cookie 頭
    Content-Type: text/html;charset=utf-8
    Set-Cookie: BD_CK_SAM=1;path=/
3. 響應體
    html文字/json文字/js/css/圖片...

4. post請求體文字引數格式

1. Content-Type: application/x-www-form-urlencoded;charset=utf-8
    用於鍵值對引數,引數的鍵值用=連線, 引數之間用&連線
    例如: name=%E5%B0%8F%E6%98%8E&age=12
2. Content-Type: application/json;charset=utf-8
    用於json字串引數
    例如: {"name": "%E5%B0%8F%E6%98%8E", "age": 12}
3. Content-Type: multipart/form-data
    用於檔案上傳請求

5. 常見響應狀態碼

200	OK                     請求成功。一般用於GET與POST請求
201 Created                已建立。成功請求並建立了新的資源
401 Unauthorized           未授權/請求要求使用者的身份認證
404 Not Found              伺服器無法根據客戶端的請求找到資源
500 Internal Server Error  伺服器內部錯誤,無法完成請求

6. 不同型別的請求及其作用:

1. GET: 從伺服器端讀取資料
2. POST: 向伺服器端新增新資料
3. PUT: 更新伺服器端已經資料
4. DELETE: 刪除伺服器端資料

7. API的分類

1. REST API:    restful
    傳送請求進行CRUD哪個操作由請求方式來決定
    同一個請求路徑可以進行多個操作
    請求方式會用到GET/POST/PUT/DELETE
2. 非REST API:   restless
    請求方式不決定請求的CRUD操作
    一個請求路徑只對應一個操作
    一般只有GET/POST

測試: 可以使用json-server快速搭建模擬的rest api 介面

XHR

1. 理解

使用XMLHttpRequest (XHR)物件可以與伺服器互動, 也就是傳送ajax請求
前端可以獲取到資料,而無需讓整個的頁面重新整理。
這使得Web頁面可以只更新頁面的區域性,而不影響使用者的操作。

2. 區別一般的HTTP請求與ajax請求

ajax請求是一種特別的http請求
對伺服器端來說, 沒有任何區別, 區別在瀏覽器端
瀏覽器端發請求: 只有XHR或fetch發出的才是ajax請求, 其它所有的都是非ajax請求
瀏覽器端接收到響應
    一般請求: 瀏覽器一般會直接顯示響應體資料, 也就是我們常說的重新整理/跳轉頁面
    ajax請求: 瀏覽器不會對介面進行任何更新操作, 只是呼叫監視的回撥函式並傳入響應相關資料

3. 使用語法

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): 設定請求頭

4.XHR的簡單封裝

/* 
    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, params={}, data={}, method='GET'}) {
      // 返回一個promise物件
      return new Promise((resolve, reject) => {
        // 建立一個XHR物件
        const xhr = new XMLHttpRequest()
        
        // 根據params拼接query引數(拼接到url上)  id=1&xxx=abc
        /* 
        {
          id: 1,
          xxx: 'abc'
        }
        */
        let queryStr = Object.keys(params).reduce((pre, key) => {
          pre += `&${key}=${params[key]}`
          return pre
        }, '')
        if (queryStr.length>0) {
          queryStr = queryStr.substring(1)
          url += '?' + queryStr
        }
        /*另一種拼接
        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
        }
*/
        // 請求方式轉換為大寫
        method = method.toUpperCase()
        
        // 初始化一個非同步請求(還沒發請求)
        xhr.open(method, url, true)
        // 繫結請求狀態改變的監聽
        xhr.onreadystatechange = function () {
          // 如果狀態值不為4, 直接結束(請求還沒有結束)
          if (xhr.readyState !== 4) {
            return
          }
          // 如果響應碼在200~~299之間, 說明請求都是成功的
          if (xhr.status>=200 && xhr.status<300) {
            // 準備響應資料物件
            const responseData = {
              data: xhr.response,
              status: xhr.status,
              statusText: xhr.statusText
            }
            // 指定promise成功及結果值
            resolve(responseData)
          } else { // 請求失敗了
            // 指定promise失敗及結果值
            const error = new Error('request error staus '+ xhr.status)
            reject(error)
          }
        }

        // 指定響應資料格式為json ==> 內部就是自動解析好
        //不再單獨需要對響應的json字串轉換為json物件 JSON.parse(xhr.response)
        xhr.responseType = 'json'

        // 如果是post/put請求
        if (method==='POST' || method==='PUT') {
          // 設定請求頭: 使請求體引數以json形式傳遞
          xhr.setRequestHeader('Content-Type', 'application/json;charset=utf-8')
          // 傳送json格式請求體引數,得轉換為json字串
          const dataJson = JSON.stringify(data) 
          // 傳送請求, 指定請求體資料
          xhr.send(dataJson)
        } else {// GET/DELETE請求
          // 傳送請求
          xhr.send(null)
        }
      })
    }

axios

1. axios的特點

基本promise的非同步ajax請求庫
瀏覽器端/node端都可以使用
支援請求/響應攔截器
支援請求取消
請求/響應資料轉換
批量傳送多個請求

PS:JQuery發ajax請求必須得先指定回撥,但axios不用

2. 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.spread(): 用來指定接收所有成功資料的回撥函式的方法

3. 難點語法理解與使用

1). axios.create(config)   #返回的是函式物件
    a. 根據指定配置建立一個新的axios, 也就就每個新axios都有自己的配置
    b. 新axios只是沒有取消請求和批量發請求的方法, 其它所有語法都是一致的
    c. 為什麼要設計這個語法?
        需求: 專案中有部分介面需要的配置與另一部分介面需要的配置不太一樣, 如何處理
        解決: 建立2個新axios, 每個都有自己特有的配置, 分別應用到不同要求的介面請求中
    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')
2). 攔截器函式/ajax請求/請求的回撥函式的呼叫順序
    a. 說明: 呼叫axios()並不是立即傳送ajax請求, 而是需要經歷一個較長的流程
    b. 流程: 請求攔截器2 => 請求攔截器1 => 發ajax請求 => 響應攔截器1 => 響應攔截器2 => 請求的回撥
    c. 注意: 此流程是通過promise串連起來的, 請求攔截器傳遞的是config, 響應攔截器傳遞的是response
            錯誤流程控制與錯誤處理

在這裡插入圖片描述

    /* 
    requestInterceptors: [{fulfilled1(){}, rejected1(){}}, {fulfilled2(){}, rejected2(){}}]
    responseInterceptors: [{fulfilled11(){}, rejected11(){}}, {fulfilled22(){}, rejected22(){}}]
    chain: [
      fulfilled2, rejected2, fulfilled1, rejected1, 
      dispatchReqeust, undefined, 
      fulfilled11, rejected11, fulfilled22, rejected22
    
    ]
    promise鏈回撥: config 
                  => (fulfilled2, rejected2) => (fulfilled1, rejected1)   // 請求攔截器處理
                  => (dispatchReqeust, undefined) // 發請求
                  => (fulfilled11, rejected11) => (fulfilled22, rejected22) // 響應攔截器處理
                  => (onResolved, onRejected) // axios發請求回撥處理
    */

    // 新增請求攔截器(回撥函式)
    axios.interceptors.request.use(
      config => {
        console.log('request interceptor1 onResolved()')
        return config  //同promise將成功繼續傳遞   否則沒reuturn返回的promise值為undifined
      },
      error => {
        console.log('request interceptor1 onRejected()')
        return Promise.reject(error);  //同promise,異常傳遞
      }
    )
    axios.interceptors.request.use(
      config => {
        console.log('request interceptor2 onResolved()')
        return config
      },
      error => {
        console.log('request interceptor2 onRejected()')
        return Promise.reject(error);
      }
    )
    // 新增響應攔截器
    axios.interceptors.response.use(
      response => {
        console.log('response interceptor1 onResolved()')
        return response
      },
      function (error) {
        console.log('response interceptor1 onRejected()')
        return Promise.reject(error);
      }
    )
    axios.interceptors.response.use(
      response => {
        console.log('response interceptor2 onResolved()')
        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)
      })
3). 取消請求
    a. 基本流程: 
        配置cancelToken物件
        快取用於取消請求的cancel函式
        在後面特定時機呼叫cancel函式取消請求
        在錯誤回撥中判斷如果error是cancel, 做相應處理
    b. 實現功能
        點選按鈕, 取消某個正在請求中的請求
        在請求一個介面前, 取消前面一個未完成的請求

PS:取消請求成功,則進入失敗回撥,此時error是cancel物件,也有message屬性

<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>

    // 新增請求攔截器
    axios.interceptors.request.use((config) => {
      // 在準備發請求前, 取消未完成的請求
      if (typeof cancel==='function') {
          cancel('取消請求')
      }
      // 新增一個cancelToken的配置
      config.cancelToken = new axios.CancelToken((c) => { // c是用於取消當前請求的函式
        // 儲存取消函式, 用於之後可能需要取消當前請求
        cancel = c
      })

      return config
    })

    // 新增響應攔截器
    axios.interceptors.response.use(
      response => {
        cancel = null
        return response
      },
      error => {
        if (axios.isCancel(error)) {// 取消請求的錯誤
          // cancel = null
          console.log('請求取消的錯誤', error.message) // 做相應處理
          // 中斷promise連結
          return new Promise(() => {})
        } else { // 請求出錯了
          cancel = null
          // 將錯誤向下傳遞
          // throw error
          return Promise.reject(error)
        }
      }
    )


    let cancel  // 用於儲存取消請求的函式
    function getProducts1() {
      axios({
        url: 'http://localhost:4000/products1',
      }).then(
        response => {
          console.log('請求1成功了', response.data)
        },
        error => {// 只用處理請求失敗的情況, 取消請求的錯誤的不用
          console.log('請求1失敗了', error.message)          
        }
      )
    }

    function getProducts2() {

      axios({
        url: 'http://localhost:4000/products2',
      }).then(
        response => {
          console.log('請求2成功了', response.data)
        },
        error => {
          console.log('請求2失敗了', error.message)
        }
      )
    }

    function cancelReq() {
      // alert('取消請求')
      // 執行取消請求的函式
      if (typeof cancel === 'function') {
        cancel('強制取消請求')
      } else {
        console.log('沒有可取消的請求')
      }
    }
  </script>
</body>

相關文章