前言
在前端異步請求中,要獲取數據,傳統的寫法是ajax回調,但這種方法
不利於代碼的維護和可讀性,所以es6時代通過Generator和Promise解決了這種問題,es7更是通過async和await使其更加簡潔
通過Generator替代回調嵌套
Generator的特點是初始化後,調用一次next方法會暫停在yield關鍵字前,同時也可以在next方法裏傳值,使對應的yield語句獲取到
- 首先編寫準備代碼
const axios = require('axios')
const http = axios.create({
baseURL: 'http://127.0.0.1:89',
timeout: 3000,
headers: {'Accept': 'application/json'}
});
let it;
定義的it是迭代器的意思,現在還沒有值
- 異步請求調用方法
function call(url,options={}) {
setTimeout(()=>{
if ( !it ) {
throw new Error('請初始化生成器')
}
http(url,options)
.then(v=>{
it.next(v.data) // 生成器傳遞參數,並且啓動下一次執行
})
},0)
}
用setTimeout
包裹,是確保這段代碼是異步執行,如果不加,同步執行的it判斷可能會拋出異常
- 編寫生成器,這裏是主要的異步請求邏輯處理
/**
多個請求互相依賴
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)
}
- 調用,運行
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
可以進一步完善
-
簡化
call
方法function call(url,options={}) { return http(url,options) }
去掉在call裏執行it.next
-
增加外部調用生成器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()) //自運行 }
-
運行
run(getData)
運行方法一中的getData生成器,得到的結果一樣
-
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裏寫一些異常,或者測試處理,類似React
的dva
處理方式**
ES7處理方式
如果你覺得方案二還是有些繁瑣,那麼可以試試ES7的await
語法
- 改造
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
數據
- 運行
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() //運行
以上代碼僅完成了核心功能,一些防禦性和異常處理不完善,僅供理解和學習