ES6+ Generator 函數(shù)應用
1. 前言
上一節(jié)我們注意學習了生成器的概念和基本用法,并通過兩個案例來說明。但是生成器更加廣泛和設計之初是為了解決異步而產(chǎn)生的。我們會通過一個開發(fā)中常見的問題入手來看 生成器函數(shù)到底是怎么來解決異步調用的問題,并且會實現(xiàn)一個簡版的 co 庫。
2. 案例
在開發(fā)過程中會遇到一個很常見的需求,我們想獲取一個值,但不能直接拿到,我們只能先請求一個接口如:api_1,獲取這個值的接口地址如:api_2。然后,請求 api_2 接口才能獲取這個值。這是一個典型的需要異步回調才能完成的功能。
在學習 Promise 的時候我們也針對這樣的情況,我們可以使用 Promise 來完成這樣的功能:
var promise = function (url) {
return new Promise((resolve, reject) => {
ajax(url, (data) => {
resolve(data) // 成功
}, (error) => {
reject(error) // 失敗
})
})
}
promise('api_1').then(res1 => {
promise(res1).then(res2 => {
console.log(res2)
})
})
從上面的代碼中可以看出,在這種情況下,使用 Promise 好像并沒有解決回調地獄的問題。那如何解決這種問題呢?我們想到了 Generator 函數(shù)具有暫停功能,那是不是我們可以讓請求 api_2 接口時暫停,等到 api_1 請求成功獲取到地址后再去請求呢?按照這個思路我們可以有下面的代碼:
const ajax = function(api) {
return new Promise((resolve, reject) => {
setTimeout(() => {
if (api === 'api_1') {
resolve('api_2');
}
if (api === 'api_2') {
resolve(100);
}
}, 0)
})
}
function * getValue() {
const api = yield ajax('api_1');
const value = yield ajax(api);
return value;
}
console.log(getValue()); // Object [Generator] {}
上面的代碼是我們模擬 ajax 請求,通過使用生成器函數(shù)寫出的代碼讓我們感覺有了同步的感覺,但是這樣去執(zhí)行 getValue 函數(shù)是不會得到結果的。那么我們要怎樣去獲得結果呢?根據(jù)生成器函數(shù)的特點,可以這樣寫:
let it = getValue();
let { value } = it.next();
value.then((data) => {
let { value } = it.next(data);
value.then((data) => {
Console.log(data);
});
});
從上面的代碼中看出還是有嵌套,好像并沒有解決問題。但如果你細心,你會發(fā)現(xiàn)每個回調的邏輯基本都是一樣的。那么我們能不能對這樣的嵌套函數(shù)進行封裝呢?答案當然是可以的,有個庫就專門解決了這個痛點 —— co 庫,有興趣的可以去研究一下這個庫,代碼很少,下面我們就來封裝一個這樣的庫。
先讓我們看看 co 庫是怎么使用的:
co(getValue()).then(res => {
console.log(res);
})
從上面的代碼中可以看出,把函數(shù)傳入進去,并讓函數(shù)執(zhí)行,然后在 then 的成功回調中可以獲取 getValue
函數(shù)返回的最終結果。這樣非常清晰地解決了上面我們需要手動執(zhí)行的方法,下面我來分析一下具體的實現(xiàn)步驟:
- 從上面的用法可以看出,co 返回的是一個 Promise 實例,所以我們需要返回一個
new Promise()
實例; - 傳入的生成器函數(shù)執(zhí)行后,我們可以調用 next () 函數(shù)拿到返回的值和是否執(zhí)行完的狀態(tài),判斷 done 如果是 true 時說明執(zhí)行完了,可以執(zhí)行 resolve;
- 當生成器函數(shù)沒有執(zhí)行完時,這時我們就需要遞歸地去調用這個 next () 來執(zhí)行下一步,因為傳入的值是一個 Promise 實例,要想獲取它的結果就需要鏈式調用 then 方法,然后拿到結果進行遞歸執(zhí)行。
有了上面的步驟分析,不難得到下面的代碼:
function co(it) {
return new Promise((resolve, reject) => {
function next(data) {
let { value, done } = it.next(data);
if (done) {
resolve(value);
} else {
Promise.resolve(value).then(data => {
next(data);
}, reject)
}
}
next(undefined);
})
}
上面的代碼中需要注意的是,如果 yield 返回的不是一個 Promise 對象時,我們對 value 使用了 Promise.resolve()
進行了包裝,這樣就可以處理返回一個普通值時沒有 then 方法的問題。
3. 小結
本節(jié)主要講解了 Generator 函數(shù)在異步中的應用,解決了某些場景下還會產(chǎn)生回調地獄的問題,通過封裝 co 方法讓我們的代碼寫起來像是同步一樣,但是 Generator 函數(shù)還不是我們解決異步的終極方案,下一節(jié)我們將學習 async 函數(shù),看它是怎么來解決異步的。