目錄
1. async await
2. Generator
3. async await 和 Generator 區別
本文主要是學習:異步編程二三事 | Promise/async/Generator實現原理解析 中一部分內容的學習筆記,(強烈推薦閱讀原本)
(一)async await
在多個回調依賴的場景中,儘管Promise通過鏈式調用取代了回調嵌套,但過多的鏈式調用可讀性仍然不佳,流程控制也不方便,ES7 提出的async 函數,終於讓 JS 對於異步操作有了終極解決方案,簡潔優美地解決了以上兩個問題。
async/await 兩個關鍵字均用來實現異步處理,使用這兩個關鍵字,可以書寫比Promise更爲簡潔的異步處理代碼。async函數可以返回Promise,當函數返回值時,Promise返回肯定結果,當async函數拋出異常時,Promise返回否定結果。
async/await實際上是對Generator(生成器)的封裝,是一個語法糖。
1. 自動執行
初步實現了一個async/await
:
function* myGenerator() {
console.log(yield Promise.resolve(1)) //1
console.log(yield Promise.resolve(2)) //2
console.log(yield Promise.resolve(3)) //3
}
function run(myGenerator) {
var g = gen(); //由於每次gen()獲取到的都是最新的迭代器,因此獲取迭代器操作要放在step()之前,否則會進入死循環
function step(val) { //封裝一個方法, 遞歸執行next()
var res = g.next(val) //獲取迭代器對象,並返回resolve的值
if (res.done) return res.value; //遞歸終止條件
res.value.then(val => { //Promise的then方法是實現自動迭代的前提
step(val) //等待Promise完成就自動執行下一個next,並傳入resolve的值
})
}
step(); //第一次執行
}
2.返回Promise & 異常處理
上面實現了Generator的自動執行以及讓yield返回resolve的值,但上邊的代碼還存在着幾點問題:
- 需要兼容基本類型:這段代碼能自動執行的前提是
yield
後面跟Promise,爲了兼容後面跟着基本類型值的情況,我們需要把yield跟的內容(gen().next.value
)都用Promise.resolve()
轉化一遍 - 缺少錯誤處理:上邊代碼裏的Promise如果執行失敗,就會導致後續執行直接中斷,我們需要通過調用
Generator.prototype.throw()
,把錯誤拋出來,才能被外層的try-catch捕獲到 - 返回值是Promise:
async/await
的返回值是一個Promise,我們這裏也需要保持一致,給返回值包一個Promise
我們改造一下run方法:
function run(gen) {
//把返回值包裝成promise
return new Promise((resolve, reject) => {
var g = gen()
function step(val) {
//錯誤處理
try {
var res = g.next(val)
} catch(err) {
return reject(err);
}
if(res.done) {
return resolve(res.value);
}
//res.value包裝爲promise,以兼容yield後面跟基本類型的情況
Promise.resolve(res.value).then(
val => {
step(val);
},
err => {
//拋出錯誤
g.throw(err)
});
}
step();
});
}
可以測試一下:
function* myGenerator() {
try {
console.log(yield Promise.resolve(1))
console.log(yield 2) //2
console.log(yield Promise.reject('error'))
} catch (error) {
console.log(error)
}
}
const result = run(myGenerator) //result是一個Promise
//輸出 1 2 error
(二)Generator
// 生成器函數根據yield語句將代碼分割爲switch-case塊,後續通過切換_context.prev和_context.next來分別執行各個case
function gen$(_context) {
while (1) {
switch (_context.prev = _context.next) {
case 0:
_context.next = 2;
return 'result1';
case 2:
_context.next = 4;
return 'result2';
case 4:
_context.next = 6;
return 'result3';
case 6:
case "end":
return _context.stop();
}
}
}
// 低配版context
var context = {
next:0,
prev: 0,
done: false,
stop: function stop () {
this.done = true
}
}
// 低配版invoke
let gen = function() {
return {
next: function() {
value = context.done ? undefined: gen$(context)
done = context.done
return {
value,
done
}
}
}
}
// 測試使用
var g = gen()
g.next() // {value: "result1", done: false}
g.next() // {value: "result2", done: false}
g.next() // {value: "result3", done: false}
g.next() // {value: undefined, done: true}
這段代碼並不難理解,我們分析一下調用流程:
我們定義的function*
生成器函數被轉化爲以上代碼
- 轉化後的代碼分爲三大塊:
gen$(_context)
由yield分割生成器函數代碼而來context對象
用於儲存函數執行上下文invoke()方法
定義next(),用於執行gen$(_context)來跳到下一步
- 當我們調用
g.next()
,就相當於調用invoke()方法
,執行gen$(_context)
,進入switch語句,switch根據context的標識,執行對應的case塊,return對應結果 - 當生成器函數運行到末尾(沒有下一個yield或已經return),switch匹配不到對應代碼塊,就會return空值,這時
g.next()
返回{value: undefined, done: true}
從中我們可以看出,Generator實現的核心在於上下文的保存
,函數並沒有真的被掛起,每一次yield,其實都執行了一遍傳入的生成器函數,只是在這個過程中間用了一個context對象儲存上下文,使得每次執行生成器函數的時候,都可以從上一個執行結果開始執行,看起來就像函數被掛起了一樣
(一)async await 和 Generator 區別
async/await
實際上是對Generator
(生成器)的封裝,async
函數是Generator
函數的語法糖,將Generator
的星號換成async
,將yield
換成await
,async
函數比Generator
函數更好用.
Generator
與async function
都是返回一個特定類型的對象:
async/await
自帶執行器,不需要手動調用next()就能自動執行下一步async
始終返回一個Promise
,使用await
或者.then()
來獲取返回值,而Generator返回的是生成器對象,一個類似{ value: XXX, done: true }
這樣結構的Object
await
能夠返回Promise的resolve/reject的值