若是讓你手寫async函數的實現,你是否是會以爲很複雜?這篇文章帶你用20行搞定它的核心。javascript
常常有人說async函數是generator函數的語法糖,那麼究竟是怎麼樣一個糖呢?讓咱們來一層層的剝開它的糖衣。前端
有的同窗想說,既然用了generator函數何須還要實現async呢?java
這篇文章的目的就是帶你們理解清楚async和generator之間究竟是如何相互協做,管理異步的。git
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函數表達,會是怎麼樣的呢?github
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的位置。promise
利用這個特性,咱們只要編寫一個自動執行的函數,就可讓這個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)) 複製代碼
那麼大致上的思路已經肯定了,babel
asyncToGenerator
接受一個generator
函數,返回一個promise
,markdown
關鍵就在於,裏面用yield
來劃分的異步流程,應該如何自動執行。app
在編寫這個函數以前,咱們先模擬手動去調用這個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
,注意
const data = yield getData() 複製代碼
這段代碼要切割成左右兩部分來看,第一次調用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文件 的代碼能夠直接放進瀏覽器裏運行,歡迎調戲。
本文用最簡單的方式實現了asyncToGenerator這個函數,這是babel編譯async函數的核心,固然在babel中,generator函數也被編譯成了一個很原始的形式,本文咱們直接以generator替代。
這也是實現promise串行的一個很棒的模式,若是本篇文章對你有幫助,點個贊就好啦。
1.若是本文對你有幫助,就點個贊支持下吧,你的「贊」是我創做的動力。
2.關注公衆號前端從進階到入院!不按期推送高質量原創文章哦。