ES6+ Generator 函數(shù)應(yīng)用
1. 前言
上一節(jié)我們注意學(xué)習(xí)了生成器的概念和基本用法,并通過(guò)兩個(gè)案例來(lái)說(shuō)明。但是生成器更加廣泛和設(shè)計(jì)之初是為了解決異步而產(chǎn)生的。我們會(huì)通過(guò)一個(gè)開(kāi)發(fā)中常見(jiàn)的問(wèn)題入手來(lái)看 生成器函數(shù)到底是怎么來(lái)解決異步調(diào)用的問(wèn)題,并且會(huì)實(shí)現(xiàn)一個(gè)簡(jiǎn)版的 co 庫(kù)。
2. 案例
在開(kāi)發(fā)過(guò)程中會(huì)遇到一個(gè)很常見(jiàn)的需求,我們想獲取一個(gè)值,但不能直接拿到,我們只能先請(qǐng)求一個(gè)接口如:api_1,獲取這個(gè)值的接口地址如:api_2。然后,請(qǐng)求 api_2 接口才能獲取這個(gè)值。這是一個(gè)典型的需要異步回調(diào)才能完成的功能。
在學(xué)習(xí) Promise 的時(shí)候我們也針對(duì)這樣的情況,我們可以使用 Promise 來(lái)完成這樣的功能:
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 好像并沒(méi)有解決回調(diào)地獄的問(wèn)題。那如何解決這種問(wèn)題呢?我們想到了 Generator 函數(shù)具有暫停功能,那是不是我們可以讓請(qǐng)求 api_2 接口時(shí)暫停,等到 api_1 請(qǐng)求成功獲取到地址后再去請(qǐng)求呢?按照這個(gè)思路我們可以有下面的代碼:
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 請(qǐng)求,通過(guò)使用生成器函數(shù)寫(xiě)出的代碼讓我們感覺(jué)有了同步的感覺(jué),但是這樣去執(zhí)行 getValue 函數(shù)是不會(huì)得到結(jié)果的。那么我們要怎樣去獲得結(jié)果呢?根據(jù)生成器函數(shù)的特點(diǎn),可以這樣寫(xiě):
let it = getValue();
let { value } = it.next();
value.then((data) => {
let { value } = it.next(data);
value.then((data) => {
Console.log(data);
});
});
從上面的代碼中看出還是有嵌套,好像并沒(méi)有解決問(wèn)題。但如果你細(xì)心,你會(huì)發(fā)現(xiàn)每個(gè)回調(diào)的邏輯基本都是一樣的。那么我們能不能對(duì)這樣的嵌套函數(shù)進(jìn)行封裝呢?答案當(dāng)然是可以的,有個(gè)庫(kù)就專(zhuān)門(mén)解決了這個(gè)痛點(diǎn) —— co 庫(kù),有興趣的可以去研究一下這個(gè)庫(kù),代碼很少,下面我們就來(lái)封裝一個(gè)這樣的庫(kù)。
先讓我們看看 co 庫(kù)是怎么使用的:
co(getValue()).then(res => {
console.log(res);
})
從上面的代碼中可以看出,把函數(shù)傳入進(jìn)去,并讓函數(shù)執(zhí)行,然后在 then 的成功回調(diào)中可以獲取 getValue
函數(shù)返回的最終結(jié)果。這樣非常清晰地解決了上面我們需要手動(dòng)執(zhí)行的方法,下面我來(lái)分析一下具體的實(shí)現(xiàn)步驟:
- 從上面的用法可以看出,co 返回的是一個(gè) Promise 實(shí)例,所以我們需要返回一個(gè)
new Promise()
實(shí)例; - 傳入的生成器函數(shù)執(zhí)行后,我們可以調(diào)用 next () 函數(shù)拿到返回的值和是否執(zhí)行完的狀態(tài),判斷 done 如果是 true 時(shí)說(shuō)明執(zhí)行完了,可以執(zhí)行 resolve;
- 當(dāng)生成器函數(shù)沒(méi)有執(zhí)行完時(shí),這時(shí)我們就需要遞歸地去調(diào)用這個(gè) next () 來(lái)執(zhí)行下一步,因?yàn)閭魅氲闹凳且粋€(gè) Promise 實(shí)例,要想獲取它的結(jié)果就需要鏈?zhǔn)秸{(diào)用 then 方法,然后拿到結(jié)果進(jìn)行遞歸執(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 返回的不是一個(gè) Promise 對(duì)象時(shí),我們對(duì) value 使用了 Promise.resolve()
進(jìn)行了包裝,這樣就可以處理返回一個(gè)普通值時(shí)沒(méi)有 then 方法的問(wèn)題。
3. 小結(jié)
本節(jié)主要講解了 Generator 函數(shù)在異步中的應(yīng)用,解決了某些場(chǎng)景下還會(huì)產(chǎn)生回調(diào)地獄的問(wèn)題,通過(guò)封裝 co 方法讓我們的代碼寫(xiě)起來(lái)像是同步一樣,但是 Generator 函數(shù)還不是我們解決異步的終極方案,下一節(jié)我們將學(xué)習(xí) async 函數(shù),看它是怎么來(lái)解決異步的。