天天看點

前端axios攔截器封裝:await遇上reject時catch的優雅處理方式

作者:秘密菜單

前端項目中使用 axios 請求接口,基于 axios 二次封裝了一些業務邏輯,一般我們會在請求和響應攔截器裡添加自己項目相關的業務邏輯,一個簡單的 demo 如下:

import axios from 'axios'
import config from '@/config'
import cookies from 'vue-cookies'

axios.defaults.baseURL = config.apiBaseURL

// 請求攔截
axios.interceptors.request.use(
  config => {
    config.timeout = 600000
    let token = cookies.get('token')
    if (token) {
      config.headers.token = token
    }
    config.headers = Object.assign(config.headers, {
      'Content-Type': 'application/json'
    })
    return config
  },
  error => {
    return Promise.reject(error)
  }
)

// 響應攔截
axios.interceptors.response.use(
  function(response) {
    // 2xx 範圍内的狀态碼都會觸發該函數
    return response.data || {}
  },
  function(error) {
    // 超出 2xx 範圍的狀态碼都會觸發該函數
    return Promise.reject(error)
  }
)

export default axios           

正常我們項目中使用是沒問題的,比如在 vue 項目中使用:

<script>
export default {
  created() {
    this.getData()
  },

  methods: {
    async getData() {
      const res = await this.$axios({
        method: 'get',
        url: 'user'
      })
      if (res.success) {
        console.log('success')
      } else {
        console.log('error')
      }
    }
  }
}
</script>           

但是有一天後端告訴你,我們架構在外層直接做了參數校驗、登入校驗...就是如果各種亂七八糟的校驗不通過時,http狀态碼就不是我們正常見到的 200 喲,可能登入态 token 失效直接扔給前端一個 401。然後問後端可不可以改下,這種情況應該也是 200 才對,因為請求已經成功,然後具體的你們的校驗邏輯可以放在裡層資料的 code 裡,前端也比較好處理。

後端說實作不了,這是架構做的,壓根還沒請求到具體的服務裡,好吧,那隻能自己動手豐衣足食了。

像這種非 2xx 的狀态碼,axios 的相應攔截器會直接走第 2 個 error,封裝的是直接傳回 Promise.reject(error),這樣前端如果不去做任何處理的話,控制台會報錯:Uncaught (in promise),也就是有一個異常未去捕捉。

要解決這個報錯就需要前端在具體的調用地方用 catch 或者 try catch 去捕捉,如果是直接在 await 的後面 catch 捕捉,報錯确實消失了,但是 await 的傳回值 res 此時就是 undefined,此時 await 接收到的值就是 catch 裡的傳回值,如果沒有 return 就是 undefined 了。

然後悲催地跟着後面如果有用 res 做判斷,又會報錯:TypeError: Cannot read properties of undefined...如果還不明白,可以看下面的小 demo:

async test() {
  const res = await new Promise((resolve, reject) => {
    setTimeout(() => {
      reject('fail')
    }, 3000)
  })
  // 報錯:Uncaught (in promise)
  // 下面的代碼不會執行
  console.log('res:', res)
  if (res.success) {
    console.log('success')
  }
},

async testCatch() {
  const res = await new Promise((resolve, reject) => {
    setTimeout(() => {
      reject('fail')
    }, 3000)
  }).catch(error => console.log(error))

  console.log('res:', res) // undefined
  if (res.success) { // 報錯:TypeError: Cannot read properties of undefined
    console.log('success')
  }
},           

最終實作方式

頁面裡用 try catch 或者自己多加幾個判斷,确實能解決報錯,但是好像也不太優雅,目前找到的比較好的方式,如果能夠拿到 error 裡的一些報錯資訊,自己模拟組裝一份資料,直接 return 給前端,這樣既不用額外 catch,也解決了後續的報錯問題:

import axios from 'axios'
import config from '@/config'
import cookies from 'vue-cookies'

axios.defaults.baseURL = config.apiBaseURL

// 請求攔截
axios.interceptors.request.use(
  config => {
    config.timeout = 600000
    let token = cookies.get('token')
    if (token) {
      config.headers.token = token
    }
    config.headers = Object.assign(config.headers, {
      'Content-Type': 'application/json'
    })
    return config
  },
  error => {
    loadingInstance && loadingInstance.close()
    if (error.response && error.response.status == 401) {
      window.location.href = '/login'
    }
    return Promise.reject(error)
  }
)

// 響應攔截
axios.interceptors.response.use(
  function(response) {
    // 2xx 範圍内的狀态碼都會觸發該函數
    return response.data || {}
  },
  function(error) {
    // 超出 2xx 範圍的狀态碼都會觸發該函數
    let errMsg
    const response = error.response
    if (response) {
      onst data = response.data || {}
      const dataMsg = data.message || data.msg
      const statusText = response.statusText
      errMsg = dataMsg || statusText || '出錯了'
    } else if (error.message) {
      errMsg = error.message
    }

    // 有錯誤資訊時自己模拟一份錯誤資料傳回,解決後端動不動就直接 http 狀态碼 500、401...
    // 直接 reject 的話外層需要自己 catch,還需要額外判斷 await 的傳回值
    if (errMsg) {
      return {
        message: errMsg,
        success: false,
      }
    }
    return Promise.reject(error)
  }
)

export default axios           

繼續閱讀