Vue項目總結(4)-API+token處理流程

本文介紹了一些Vue和axios的實用技巧,解決前端API調用中access_token的處理問題,包括:Promise的鏈式調用,axios的攔截器,vue-router記錄導航歷史等。javascript

參考項目:github.com/jasony62/tm…前端

問題分析

先後端徹底分離的項目中,一個前端應用會訪問多個後端的API,API調用都要經過傳遞token進行用戶身份認證。用戶登陸就是用用戶名和口令換取token,得到token後前端自行保留(例如:放在sessionStorage裏),而後每次發起API調用時添加上這個參數。爲了安全,token會設置有效期,過時了就須要從新登陸獲取新的token。咱們能夠看到用戶登陸流程設計的核心,其實就是一個管理和使用token的問題。vue

基於token的使用,須要考慮以下狀況:java

  • 進入頁面,頁面內同時發起多個API調用
  • 用戶在頁面中執行操做,操做中發起多個API調用,例如:提交表單
  • 調用API,本地沒有token,打開登陸頁
  • 調用API,本地有token,API調用返回token已通過期,打開登陸頁
  • 調用API,其餘請求正在獲取token,中止或取消調用
  • 調用認證API,不添加token
  • 登陸成功後,保存得到的token,關閉登陸,從新發起API調用

這裏面臨幾個技術問題:ios

  • 前端本地存儲token;
  • 區分API調用是否須要添加token;
  • 若是同時發出多個請求,當其中的一個已經開始獲取token,如何取消或暫停其它請求;
  • 登陸後完成後,如何自動回到以前的狀態。

理解axios攔截器

爲了知足上面提到的要求,須要可以控制API請求的執行過程,axios中是經過攔截器添加控制邏輯,由於咱們先深刻了解一下axios中攔截器的相關代碼。git

// Hook up interceptors middleware
var chain = [dispatchRequest, undefined];
var promise = Promise.resolve(config);

this.interceptors.request.forEach(function unshiftRequestInterceptors(interceptor) {
  chain.unshift(interceptor.fulfilled, interceptor.rejected);
});

this.interceptors.response.forEach(function pushResponseInterceptors(interceptor) {
  chain.push(interceptor.fulfilled, interceptor.rejected);
});

while (chain.length) {
  promise = promise.then(chain.shift(), chain.shift());
}

return promise;
複製代碼

要理解上面的代碼,首先要理解promise鏈式調用promise.then()github

鏈式調用

prmise鏈式調用就將幾個promise串起來執行,上一個promise執行的結果,做爲下一個promise的輸入。看個例子:vue-router

let p1 = Promise.resolve('a')
let p2 = Promise.resolve('b')
let p3 = Promise.resolve('c')
let p4

p4 = p1.then(v1 => {
  console.log('then-1', v1) // 這個是第2行輸出,輸出a
  return p2.then(v2 => v1 + v2)
}).then(v1 => {
  console.log('then-2', v1) // 這個是第3行輸出,輸出ab
  return p3.then(v2 => v1 +v2)
})

p4.then(v => {
  console.log('then-3', v) // 這個是第4行輸出,輸出abc
})

console.log('begin...') // 這個是第1行輸出

複製代碼

經過上面的方式就能夠把多個異步操做串聯起來執行。axios

then方法

Promise的then方法傳入兩個參數,分別在調用then方法的promise對象完成或失敗時調用。注意這個調用是異步調用(須要去排隊執行),這就是爲何上面的例子中最後1句console.log()是第1個輸出,由於then中的回調函數是排隊執行的。後端

掌握then方法的關鍵是理解返回值。首先,then方法返回的是Promise對象,這是能夠進行鏈式調用的基礎;第二,執行哪一個回調函數由調用then的Promise對象的執行結果決定(兩個回調函數之間沒有關係);第三,返回的Promise對象的狀態由執行的回調函數的返回值決定(和是哪一個回調函數返回無關)。例如:回調函數內返回的是一個值(數字、字符串、對象等),那麼生成的Promise對象的狀態是完成(fulfilled)。具體規則請參考在線文檔。

須要注意的是,失敗回調函數只是當前執行的promise對象的結果,並非整個鏈的結果,完成和失敗回調函數均可以經過返回值,告訴下一個環節要進入完成函數仍是失敗函數。所以,鏈式調用中每個環節均可以修正上一個環節的「錯誤」,繼續讓鏈執行。

這裏有個有意思的問題:catch必定是除finally外最後執行的環節嗎,它能夠寫在then的前面嗎?答案是能夠。由於,catchthen的縮寫,等價於then(undefined, err=>{...})

參考:developer.mozilla.org/zh-CN/docs/…

axios攔截器

明白了鏈式調用和then方法,axios的攔截器機制就好理解了。

while (chain.length) {
  promise = promise.then(chain.shift(), chain.shift());
}
複製代碼

每條攔截規則都由完成函數(fulfilled)和失敗函數(rejected)構成,能夠理解爲:請求的上一步成功了作什麼,失敗了又該作什麼。這個理解很關鍵,由於添加攔截規則時容易想成:在完成函數中添加攔截邏輯,若是這個邏輯失敗了,在失敗函數中進行處理。完成函數發生異常,失敗函數不會被執行,由於是否調用它不是由完成函數決定,而是由上一個執行環節的執行結果決定。完成函數的異常要在後續環節的失敗函數中處理。

另外,須要注意的是,請求規則和響應規則的執行順序不同,請求規則是先定義的後執行(unshift),響應規則是先定義的先執行(push)

再有,請求規則和響應規則是在同一個鏈上,所以,請求規則中的異常,能夠由響應階段失敗函數處理。例如:不管執行請求發生了什麼問題,都須要給用戶一個消息框進行說明,那麼即便是在請求階段發生的異常,也均可以放在響應攔截規則中進行統一處理。

本地存儲token

獲取token後能夠放在localStorage或者sessionStorage中,例如:

sessionStorage.setItem('access_token', token)
複製代碼

區分API是否添加token

axios支持建立新實例,能夠給不一樣的實例指定不一樣的攔截規則。

axios.create(config)
複製代碼

tms-vue項目中能夠給axios實例進行命名,而且指定不一樣的攔截規則。

Vue.TmsAxios({ name: 'file-api', rules })
Vue.TmsAxios({ name: 'auth-api' })
複製代碼

暫停API調用

經過設置攔截規則,咱們能夠對API調用的前端過程進行控制。

調用API時,圍繞token,一個axios請求可能碰到兩種狀況:一、請求階段發現token不存在,得到token後,繼續發送;二、響應階段返回token不可用,得到token後,重發請求。若是「同時」調用多個API,當前面的請求已經開始獲取token,那麼請求都應該掛起,等待新的token,不該該重複獲取token。

咱們能夠把獲取token理解爲一種須要「鎖」控制的操做,就是說只有第一個請求能夠得到鎖,進行獲取token的操做(登陸),後序的請求都被鎖住了,等待第一個請求執行的結果。一旦第1個請求執行結束,後面的請求就都得到告終果,這樣就能夠避免每一個請求都重複執行獲取token的操做。

Promise的機制能夠很好的知足上面的需求。基本思路是,咱們將登陸作成一個Promise,全部請求都等待這個Promise的執行結果。請求攔截器中添加規則(示意):

function onFulfilled(config) {
  ......
  if (requireLogin) return loginPromise
  ......
}
複製代碼

經過loginPromise就能夠將axios的請求掛起,等待登陸完成後再繼續執行。

這裏存在一個關鍵問題,loginPromise必須是共享的,全部正在發生的請求都要等待同一個Promise。可是,由於token有有效期,用戶在整個使用過程當中有可能須要屢次登陸,loginPromise一旦執行過一次就已經處於完成(fulfilled)狀態,後序的調用並不會發起新的登陸。爲了解決這個問題,須要在全部被掛起的請求被通知登陸完成後,將loginPromise刪除,再有新請求時,生成新的Promise。

爲了解決這個問題,tms-vue中實現了lock-promise組件。

onst RUNNING_LOCK_PROMISE = Symbol('running_lock_promise')

class TmsLockPromise {
  constructor(fnLockGetter) {
    this.lockGetter = fnLockGetter
    this.waitingPromises = []
  }
  isRunning() {
    return !!this[RUNNING_LOCK_PROMISE]
  }
  wait() {
    if (!this.isRunning()) {
      this[RUNNING_LOCK_PROMISE] = this.lockGetter()
    }
    let prom = new Promise(resolve => {
      this[RUNNING_LOCK_PROMISE].then(token => {
        // 刪除處理完的請求
        this.waitingPromises.splice(this.waitingPromises.indexOf(prom), 1)
        // 全部的請求都處理完,關閉登陸結果
        if (this.waitingPromises.length === 0) {
          setTimeout(() => {
            this[RUNNING_LOCK_PROMISE] = null
          })
        }
        resolve(token)
      })
    })
    this.waitingPromises.push(prom)

    return prom
  }
}

export { TmsLockPromise }
複製代碼

調用代碼以下:

let lockPromise = new TmsLockPromise(function() {
    // 返回一個須要等待執行結果的promise,例如登陸
  })
...
let pendingPromise = lockPromise.wait() 
複製代碼

lock-promise組件的核心是wait方法。每次調用該方法都會建立一個新的Promise對象,讓這個「代理」等待登陸的結果,這樣得到結果後就能夠執行一些管理狀態的操做了。

經過lock-promise就能夠實現將「同時」(在登陸過程當中)發起的請求掛起,等待「鎖」操做完成後,繼續執行全部請求。全部請求都執行後,自動清除鎖的狀態。

完成登陸後回到以前的狀態

前一部分介紹的是已經發起API調用時再處理token的狀況。咱們還能夠在進入頁面前檢查token是否已經具有,若是不具有,就跳轉到登陸頁,登陸完成後再返回要進入的頁面。這種方式適合首次進入應用的狀況。

實現這種功能要用到Vue-Router,首先,經過導航守衛機制進行檢查;第二,登陸成功後,應該可以自動返回用戶原本要訪問的頁面。

爲了解決這個問題,tms-vue中實現了router-history插件。

router.beforeEach((to, from, next) => {
  if (to.name !== 'login') { // 不是訪問登陸頁,檢查token
    let token = sessionStorage.getItem('access_token')
    if (!token) {
      Vue.TmsRouterHistory.push(to.path) // 保存原始跳轉頁
      return next('/login') // 沒有token,跳轉到登陸頁
    }
  }
  next()
})
複製代碼

登陸成功後,檢查是否要返回原來要進入的頁面:

if (this.$tmsRouterHistory.canBack()) {
  this.$router.back()
} else {
  this.$router.push('/')
}
複製代碼

總結

Promise是最重要的概念,它是實現不少複雜方案的底層機制,必需要熟練掌握!!!

解決以上問題就初步實現了「API+登陸」解決認證的關鍵技術問題,可是仍然須要進行細化,例如:登陸組件的組件化設計,失敗狀況處理等。後續文章中將繼續探討這些問題。

本系列其餘文章:

Vue項目總結(1)-基本概念+Nodejs+VUE+VSCode

Vue項目總結(2)-前端獨立測試+VUE

Vue項目總結(3)-先後端分離致使的跨域問題分析

相關文章
相關標籤/搜索