異步請求回調嵌套解決方案

前言

在前端異步請求中,要獲取數據,傳統的寫法是ajax回調,但這種方法
不利於代碼的維護和可讀性,所以es6時代通過Generator和Promise解決了這種問題,es7更是通過async和await使其更加簡潔

通過Generator替代回調嵌套

Generator的特點是初始化後,調用一次next方法會暫停在yield關鍵字前,同時也可以在next方法裏傳值,使對應的yield語句獲取到
  1. 首先編寫準備代碼
const axios = require('axios')

const http = axios.create({
  baseURL: 'http://127.0.0.1:89',
  timeout: 3000,
  headers: {'Accept': 'application/json'}
});


let it;

定義的it是迭代器的意思,現在還沒有值

  1. 異步請求調用方法
function call(url,options={}) {
    setTimeout(()=>{
        if ( !it ) {
            throw new Error('請初始化生成器')
        }
        http(url,options)
        .then(v=>{
            
            it.next(v.data)  // 生成器傳遞參數,並且啓動下一次執行
        })
    },0)
}

setTimeout包裹,是確保這段代碼是異步執行,如果不加,同步執行的it判斷可能會拋出異常

  1. 編寫生成器,這裏是主要的異步請求邏輯處理
/**
    多個請求互相依賴
    1. 根據用戶名,密碼獲取用戶憑證
    2. 根據用戶憑證獲取用戶數據id列表
    3. 獲取數據列表中第一條數據詳情
*/
function * getData() {
    const data1 = yield call('/login', { method: 'POST', body: JSON.stringify({user:'root',pass:'123456'}) })
    console.log('我是結果1:',data1)
    const data2 = yield call('/list', { method: 'POST', headers: { token: data1.data.token } })
    console.log('我是結果2:',data2)
    const data3 = yield call('/item', { method: 'POST', headers: { token: data1.data.token }, body: JSON.stringify({ id:data2.data.ids[0] }) })
    console.log('我是結果3:',data3)  
}
  1. 調用,運行
it = getData()
it.next()

這裏給it附上值,然後會觸發第2步的代碼
圖片描述

運行流程

首先我們定義了生成器,運行它的時候,只需執行it.netx,然後會運行第一個yield後面的語句,並停在第一個yield語句處,當call函數裏的異步請求執行完畢,會將異步請求的結果it.next(data)傳遞給第一個yield前面的取值語句,然後會執行到第二個yield語句後面的call,以此類推,直到整個生成器執行完畢

Generator + Promise

單個通過Generator已經可以解決大部分異步嵌套的問題,但是不夠完善,要確保it初始化,必須讓整個call異步執行,代碼不夠優雅,而且依賴外部it,結構分散,所以我們用Generator + Promise可以進一步完善

  1. 簡化call方法

    function call(url,options={}) {
      return http(url,options)
    }

去掉在call裏執行it.next

  1. 增加外部調用生成器next函數run

    function run (g) {
      const it = g();  // 初始化生成器, 注意這裏的冒號
      
      (function each(res) {
          // 根據生成器的返回結果進行判斷
          if (!res.done && res.value instanceof Promise ) {  // 如果是Promise返回
              res.value.then(v=>{
                  each( it.next(v.data) )    // 這裏是方案一的call裏的next並傳值到下一次next 
              })
          } else if (res.done) {    // 生成器執行結束, 運行結束
              return
          } else {
              throw new Error('yield 後面請用返回Promise的函數')
          }
      })(it.next())    //自運行
    }
  2. 運行

    run(getData)

運行方法一中的getData生成器,得到的結果一樣

  1. getData裏yield後的函數擴展

根據run函數可知,只要it.next返回的結果是Promise即可正常運行,那麼在getData裏如下寫法也是可以的

function * getData() {
  const data1 = yield http('/login', { method: 'POST', body: JSON.stringify({user:'root',pass:'123456'}) })
  console.log('我是結果1:',data1)
  const data2 = yield http('/list', { method: 'POST', headers: { token: data1.data.token } })
  console.log('我是結果2:',data2)
  const data3 = yield http('/item', { method: 'POST', headers: { token: data1.data.token }, body: JSON.stringify({ id:data2.data.ids[0] }) })
  console.log('我是結果3:',data3)  
}

**這裏的http函數是axios的一個實例,返回值爲Promise,
外層加call是可以在call裏寫一些異常,或者測試處理,類似Reactdva處理方式**

ES7處理方式

如果你覺得方案二還是有些繁瑣,那麼可以試試ES7的await語法

  1. 改造getData函數如下
/**
    多個請求互相依賴
    1. 根據用戶名,密碼獲取用戶憑證
    2. 根據用戶憑證獲取用戶數據id列表
    3. 獲取數據列表中第一條數據詳情
*/
async function getData() {
    const { data:data1 } = await call('/login', { method: 'POST', body: JSON.stringify({user:'root',pass:'123456'}) })
    console.log('我是結果1:',data1)
    const { data:data2 } = await call('/list', { method: 'POST', headers: { token: data1.data.token } })
    console.log('我是結果2:',data2)
    const { data:data3 } = await call('/item', { method: 'POST', headers: { token: data1.data.token }, body: JSON.stringify({ id:data2.data.ids[0] }) })
    console.log('我是結果3:',data3)  
}

和方案二比較這個方法頭部多了async關鍵字,去掉了*號,yield換成了await,這是ES7異步函數的聲明方式。
注意返回值不是通過方案二中next res.data注入,所以獲取到的是整個res,取值的時候注意拿結果裏的.data數據

  1. 運行
getData()

結果和方案一二一樣,這種方式更加簡潔易懂

方案三簡單完整代碼

個人比較喜歡簡潔有效的代碼,所以推薦方案三

const axios = require('axios')

const http = axios.create({
  baseURL: 'http://127.0.0.1:89',
  timeout: 3000,
  headers: {'Accept': 'application/json'}
});


/**
    多個請求互相依賴
    1. 根據用戶名,密碼獲取用戶憑證
    2. 根據用戶憑證獲取用戶數據id列表
    3. 獲取數據列表中第一條數據詳情
*/
async function getData() {
    const { data:data1 } = await http('/login', { method: 'POST', body: JSON.stringify({user:'root',pass:'123456'}) })
    console.log('我是結果1:',data1)
    const { data:data2 } = await http('/list', { method: 'POST', headers: { token: data1.data.token } })
    console.log('我是結果2:',data2)
    const { data:data3 } = await http('/item', { method: 'POST', headers: { token: data1.data.token }, body: JSON.stringify({ id:data2.data.ids[0] }) })
    console.log('我是結果3:',data3)  
}

getData() //運行

以上代碼僅完成了核心功能,一些防禦性和異常處理不完善,僅供理解和學習

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章