手寫async await的最簡實現
前言
如果讓你手寫async函式的實現,你是不是會覺得很複雜?這篇文章帶你用20行搞定它的核心。
經常有人說async函式是generator函式的語法糖,那麼到底是怎麼樣一個糖呢?讓我們來一層層的剝開它的糖衣。
有的同學想說,既然用了generator函式何必還要實現async呢?
這篇文章的目的就是帶大家理解清楚async和generator之間到底是如何相互協作,管理非同步的。
示例
const getData = () => new Promise(resolve => setTimeout(() => resolve("data"), 1000))
async function test() {
const data = await getData()
console.log('data: ', data);
const data2 = await getData()
console.log('data2: ', data2);
return 'success'
}
// 這樣的一個函式 應該再1秒後列印data 再過一秒列印data2 最後列印success
test().then(res => console.log(res))
思路
對於這個簡單的案例來說,如果我們把它用generator函式表達,會是怎麼樣的呢?
function* testG() {
// await被編譯成了yield
const data = yield getData()
console.log('data: ', data);
const data2 = yield getData()
console.log('data2: ', data2);
return 'success'
}
我們知道,generator函式是不會自動執行的,每一次呼叫它的next方法,會停留在下一個yield的位置。
利用這個特性,我們只要編寫一個自動執行的函式,就可以讓這個generator函式完全實現async函式的功能。
const getData = () => new Promise(resolve => setTimeout(() => resolve("data"), 1000))
var test = asyncToGenerator(
function* testG() {
// await被編譯成了yield
const data = yield getData()
console.log('data: ', data);
const data2 = yield getData()
console.log('data2: ', data2);
return 'success'
}
)
test().then(res => console.log(res))
那麼大體上的思路已經確定了,
asyncToGenerator接受一個generator函式,返回一個promise,
關鍵就在於,裡面用yield來劃分的非同步流程,應該如何自動執行。
如果是手動執行
在編寫這個函式之前,我們先模擬手動去呼叫這個generator函式去一步步的把流程走完,有助於後面的思考。
function* testG() {
// await被編譯成了yield
const data = yield getData()
console.log('data: ', data);
const data2 = yield getData()
console.log('data2: ', data2);
return 'success'
}
我們先呼叫testG生成一個迭代器
// 返回了一個迭代器
var gen = testG()
然後開始執行第一次next
// 第一次呼叫next 停留在第一個yield的位置
// 返回的promise裡 包含了data需要的資料
var dataPromise = gen.next()
這裡返回了一個promise,就是第一次getData()所返回的promise,注意
constdata =yieldgetData()
這段程式碼要切割成左右兩部分來看,第一次呼叫next,其實只是停留在了yield getData()這裡,
data的值並沒有被確定。
那麼什麼時候data的值會被確定呢?
下一次呼叫next的時候,傳的引數會被作為上一個yield前面接受的值
也就是說,我們再次呼叫gen.next('這個引數才會被賦給data變數')的時候
data的值才會被確定為'這個引數才會被賦給data變數'
gen.next('這個引數才會被賦給data變數')
// 然後這裡的data才有值
const data = yield getData()
// 然後打印出data
console.log('data: ', data);
// 然後繼續走到下一個yield
const data2 = yield getData()
然後往下執行,直到遇到下一個yield,繼續這樣的流程...
這是generator函式設計的一個比較難理解的點,但是為了實現我們的目標,還是得去學習它~
藉助這個特性,如果我們這樣去控制yield的流程,是不是就能實現非同步串行了?
function* testG() {
// await被編譯成了yield
const data = yield getData()
console.log('data: ', data);
const data2 = yield getData()
console.log('data2: ', data2);
return 'success'
}
var gen = testG()
var dataPromise = gen.next()
dataPromise.then((value1) => {
// data1的value被拿到了 繼續呼叫next並且傳遞給data
var data2Promise = gen.next(value1)
// console.log('data: ', data);
// 此時就會打印出data
data2Promise.value.then((value2) => {
// data2的value拿到了 繼續呼叫next並且傳遞value2
gen.next(value2)
// console.log('data2: ', data2);
// 此時就會打印出data2
})
})
這樣的一個看著像callback hell的呼叫,就可以讓我們的generator函式把非同步安排的明明白白。
實現
有了這樣的思路,實現這個高階函式就變得很簡單了。
先整體看一下結構,有個印象,然後我們逐行註釋講解。
function asyncToGenerator(generatorFunc) {
return function() {
const gen = generatorFunc.apply(this, arguments)
return new Promise((resolve, reject) => {
function step(key, arg) {
let generatorResult
try {
generatorResult = gen[key](arg)
} catch (error) {
return reject(error)
}
const { value, done } = generatorResult
if (done) {
return resolve(value)
} else {
return Promise.resolve(value).then(val => step('next', val), err => step('throw', err))
}
}
step("next")
})
}
}
不多不少,22行。
接下來逐行講解。
function asyncToGenerator(generatorFunc) {
// 返回的是一個新的函式
return function() {
// 先呼叫generator函式 生成迭代器
// 對應 var gen = testG()
const gen = generatorFunc.apply(this, arguments)
// 返回一個promise 因為外部是用.then的方式 或者await的方式去使用這個函式的返回值的
// var test = asyncToGenerator(testG)
// test().then(res => console.log(res))
return new Promise((resolve, reject) => {
// 內部定義一個step函式 用來一步一步的跨過yield的阻礙
// key有next和throw兩種取值,分別對應了gen的next和throw方法
// arg引數則是用來把promise resolve出來的值交給下一個yield
function step(key, arg) {
let generatorResult
// 這個方法需要包裹在try catch中
// 如果報錯了 就把promise給reject掉 外部通過.catch可以獲取到錯誤
try {
generatorResult = gen[key](arg)
} catch (error) {
return reject(error)
}
// gen.next() 得到的結果是一個 { value, done } 的結構
const { value, done } = generatorResult
if (done) {
// 如果已經完成了 就直接resolve這個promise
// 這個done是在最後一次呼叫next後才會為true
// 以本文的例子來說 此時的結果是 { done: true, value: 'success' }
// 這個value也就是generator函式最後的返回值
return resolve(value)
} else {
// 除了最後結束的時候外,每次呼叫gen.next()
// 其實是返回 { value: Promise, done: false } 的結構,
// 這裡要注意的是Promise.resolve可以接受一個promise為引數
// 並且這個promise引數被resolve的時候,這個then才會被呼叫
return Promise.resolve(
// 這個value對應的是yield後面的promise
value
).then(
// value這個promise被resove的時候,就會執行next
// 並且只要done不是true的時候 就會遞迴的往下解開promise
// 對應gen.next().value.then(value => {
// gen.next(value).value.then(value2 => {
// gen.next()
//
// // 此時done為true了 整個promise被resolve了
// // 最外部的test().then(res => console.log(res))的then就開始執行了
// })
// })
function onResolve(val) {
step("next", val)
},
// 如果promise被reject了 就再次進入step函式
// 不同的是,這次的try catch中呼叫的是gen.throw(err)
// 那麼自然就被catch到 然後把promise給reject掉啦
function onReject(err) {
step("throw", err)
},
)
}
}
step("next")
})
}
}
原始碼地址
這個js檔案的程式碼可以直接放進瀏覽器裡執行,歡迎調戲。
豌豆資源搜尋網站https://55wd.com 電腦刺繡繡花廠 ttp://www.szhdn.com
總結
本文用最簡單的方式實現了asyncToGenerator這個函式,這是babel編譯async函式的核心,當然在babel中,generator函式也被編譯成了一個很原始的形式,本文我們直接以generator替代。