簡(jiǎn)介
基本概念
Generator 是 ES6 提供的一種異步編程解決方案⊙糟澹可以把它理解成一個(gè)狀態(tài)機(jī)隆箩,并且會(huì)返回一個(gè)遍歷器對(duì)象
- 有以下兩個(gè)特征
- function關(guān)鍵字和函數(shù)名之間有一個(gè)星號(hào)
*
- 函數(shù)體內(nèi)部使用
yield
表達(dá)式
- 執(zhí)行大致過(guò)程
- 生成 Iterator 之后响驴,第一次調(diào)用
next
方法,直到遇見(jiàn)第一個(gè)yield
表達(dá)式為止拖云。此時(shí)返回一個(gè)對(duì)象贷笛,value
就是yield
表達(dá)式的值,同時(shí)done
為false - 按照此規(guī)律執(zhí)行应又,當(dāng)執(zhí)行到return語(yǔ)句是宙项,
next
方法還是返回一個(gè)對(duì)象,該對(duì)象的value
就是return
返回的值株扛,同時(shí)done
為ture - 如果沒(méi)有
return
尤筐,執(zhí)行完所有的yield
,done
還是為false
洞就。需要再來(lái)一下next
yield 表達(dá)式
- generator 函數(shù)中可以不使用
yield
盆繁,此時(shí)變成了一個(gè)單純的暫緩執(zhí)行函數(shù) - 在普通函數(shù),匿名函數(shù)中使用
yield
表達(dá)式旬蟋,會(huì)產(chǎn)生一個(gè)錯(cuò)誤 - 如果
yield
表達(dá)式在另一個(gè)表達(dá)式中油昂,必須放在圓括號(hào)內(nèi)
// 情景3示例
function* demo() {
console.log('Hello' + (yield 99)); // OK
console.log('Hello' + (yield 123)); // OK
}
next 方法參數(shù)
next 方法可以帶一個(gè)參數(shù),該參數(shù)會(huì)被當(dāng)作上一個(gè)yield表達(dá)式的返回值
基礎(chǔ)案例分析
function* foo(x) {
var y = 2 * (yield (x + 1));
var z = yield (y / 3);
return (x + y + z);
}
var b = foo(5);
b.next(); // {value:6,done:false}
b.next(12); // {value:8,done:false}
// 此時(shí) x=5,y=24,z=13
b.next(13); // {value:42,done:true}
- 第一個(gè)使用
next
不需要傳值
進(jìn)階案例分析
function* dataConsumer() {
console.log('Started');
console.log(`1. ${yield}`);
console.log(`2. ${yield}`);
return 'result';
}
let genObj = dataConsumer();
genObj.next();
genObj.next('a');
genObj.next('b');
- 第一次調(diào)用next,執(zhí)行到第二條語(yǔ)句暫停倾贰。因此打印'Started'
- 第二次調(diào)用next,傳入
a
冕碟,作為上次yield
的返回值,打印1. a
匆浙。繼續(xù)執(zhí)行到yield
暫停 - 第三次調(diào)用next,傳入
b
安寺,作為上次yield
的返回值,答應(yīng)2. b
首尼。繼續(xù)執(zhí)行挑庶,此時(shí){value:'result',done:true}
for...of
循環(huán)
斐波那契數(shù)列
function* feibonacci() {
let [prev,curr] = [0,1];
for (;;) {
[prev,curr] = [curr,prev+curr]
yield curr;
}
}
// num 是值的大小,不是個(gè)數(shù)
for (let num of feibonacci()) {
if (num > 100) break; // ---> break 會(huì)觸發(fā)遍歷器的return方法软能,return 方法其實(shí)一般就是返回`return {done:true}`
console.log(num);
}
對(duì)象擴(kuò)展遍歷器
在對(duì)象的
[Symbol.iterator]
上掛載遍歷器
function* objectEntries() {
let objKeys = Object.keys(this);
for (let key of objKeys) {
yield [key,this[key]]
}
}
let jane = { first: 'Jane', last: 'Doe'};
// 只能綁定到單個(gè)的對(duì)象上
jane[Symbol.iterator] = objectEntries;
for (let [key,val] of jane) {
console.log(key,val)
}
throw方法
遍歷器對(duì)象都有一個(gè)
throw
方法迎捺,在外部拋出錯(cuò)誤之后,內(nèi)部能夠捕獲
基礎(chǔ)知識(shí)
外部拋出錯(cuò)誤
var g = function* () {
try {
yield;
} catch (e) {
console.log('內(nèi)部捕獲',e)
}
};
var i = g();
i.next();
try {
i.throw('a');
i.throw('b');
} catch (e) {
console.log('外部捕獲',e);
}
// 內(nèi)部捕獲 a
// 外部捕獲 b
- 遍歷器
i
執(zhí)行next
方法 - 遍歷器
i
在外部拋出錯(cuò)誤查排,被內(nèi)部的捕獲凳枝。同時(shí),throw
方法傳遞的參數(shù)當(dāng)作catch
的參數(shù) - 遍歷器再次拋出錯(cuò)誤,
catch
已經(jīng)執(zhí)行過(guò)了雹嗦,就不執(zhí)行了范舀,因此被外部的try catch
捕獲 - 可以內(nèi)部捕獲外部拋出的錯(cuò)誤,也可以外部捕獲拋出的錯(cuò)誤
-
throw
方法了罪,也就是拋出錯(cuò)誤的方法锭环,默認(rèn)執(zhí)行一次next
內(nèi)部拋出錯(cuò)誤
function* foo() {
var x = yield 3;
var y = x.toUpperCase();
yield y;
}
var it = foo();
it.next(); // { value:3, done:false }
try {
it.next(42);
} catch (err) {
console.log(err);
}
- 由于
next(42)
后,x為數(shù)字42
沒(méi)有toUpperCase
方法泊藕。因此辅辩,將拋出一個(gè)錯(cuò)誤 - 拋出的錯(cuò)誤將在外部被捕獲
return
方法
- 使用
return
方法
- 當(dāng)有參數(shù)時(shí),返回給定的值
- 當(dāng)沒(méi)有參數(shù),返回
undefined
-
Generator
函數(shù)內(nèi)有try...finally
代碼塊玫锋,那么return
方法會(huì)推遲到finally
代碼執(zhí)行完再執(zhí)行
function* gen() {
yield 1;
yield 2;
}
var g = gen();
g.next(); // {value: 1,done: false}
g.return('foo'); // {value: 'foo',done: true}
g.next(); // {value: undefined,done: false}
// finally
function* numbers () {
try {
yield 2;
} finally {
yield 4;
}
yield 6;
}
var g = numbers();
g.next() // { value: 2, done: false }
g.return(7) // { value: 4, done: false } --- 執(zhí)行finally中語(yǔ)句
g.next() // { value: 7, done: true } --- 執(zhí)行完finnally中語(yǔ)句之后蛾茉,再執(zhí)行return語(yǔ)句
next,throw,return 共同點(diǎn)
它們作用都是讓 Generator 函數(shù)回復(fù)執(zhí)行
yield*
表達(dá)式
yield*
表達(dá)式,代表它后面是一個(gè)遍歷器對(duì)象撩鹿,在沒(méi)有return
語(yǔ)句的情況下谦炬,相當(dāng)于使用for of
- 在
Generator
函數(shù)中不能直接調(diào)用Generator
函數(shù),需要使用yield*
基本案例
function* foo() {
yield 'a';
yield 'b';
}
function* bar() {
yield 'x';
yield* foo();
yield 'y';
}
// Generator bar 相當(dāng)于
function* bar() {
yield 'x';
for (let v of foo()) {
yield v;
}
yield 'y';
}
// 調(diào)用 bar
for (let v of bar()) {
console.log(v);
}
// 結(jié)果是:x,a,b,y
yield*
后邊值的種類
任何數(shù)據(jù)結(jié)構(gòu)只要有 Iterator 接口节沦,就可以被yield*遍歷键思。
yield* 'hello'
yield* [1,2,3]
有return的Generator函數(shù)
yield* foo()
,如果foo函數(shù)有return甫贯,那么該表達(dá)式是有返回值的
function *foo() {
yield 2;
yield 3;
return "foo";
}
function *bar() {
yield 1;
var v = yield *foo();
console.log( "v: " + v );
yield 4;
}
var it = bar();
it.next()
// {value: 1, done: false}
it.next()
// {value: 2, done: false}
it.next()
// {value: 3, done: false}
it.next();
// 相當(dāng)于沒(méi)有調(diào)用 next 方法吼鳞,只是將返回值返回給表達(dá)式
// "v: foo"
// {value: 4, done: false}
it.next()
// {value: undefined, done: true}
取出嵌套數(shù)組
function* tree(arr) {
if (Array.isArray(arr)) {
for (let i = 0;i < arr.length;i++) {
yield* tree(arr[i]);
}
}else {
yield arr;
}
}
const arr = [1,[2,3],[4,5],6,7]
for (let i of tree(arr)) {
console.log(i);
}
- 注意兩個(gè)地方
- 如果使用
yield tree
,也就是沒(méi)有*
,那么調(diào)用Generator
方法是沒(méi)有什么效果的 - 使用
let
聲明的變量進(jìn)行遞歸沒(méi)有關(guān)系
作為對(duì)象屬性的Generator函數(shù)
簡(jiǎn)寫成下面的形式
let obj = {
* myGeneratorMethod() {
}
}
Generator 函數(shù)的this
Generator的返回值
- Generator 函數(shù)總是返回一個(gè)遍歷器叫搁,這個(gè)遍歷器是 Generator 的實(shí)例
- 使用
instanceof
檢測(cè)赔桌,為true - Generator 原型上的方法,將被遍歷器繼承
function* p() {
}
p.property.say = function () {
console.log('hello')
}
let s = p();
s instanceof p; // true
s.say(); // 'hello'
Generator 中 this 基礎(chǔ)知識(shí)
- 即使“返回值”是“Generator”函數(shù)實(shí)例渴逻,但是“Generator”中this上綁定的屬性疾党,“返回值”上并不會(huì)有
- 不能對(duì) Generator 函數(shù)直接使用 new 命令
應(yīng)用
異步操縱的同步化表達(dá)
// loading 應(yīng)用
function* loadUI() {
showLoadingScreen();
yield loadUIDataAsynchronously();
hideLoadingScreen();
}
var loader = loadUI();
loader.next();
loader.next();
- 第一次執(zhí)行
next
方法時(shí),展示加載頁(yè)面裸卫,同時(shí)發(fā)送異步請(qǐng)求 - 其實(shí)第二次執(zhí)行
next
方法仿贬,應(yīng)該是異步請(qǐng)求完畢的時(shí)候
// Ajax 應(yīng)用
function request(url){
// 是個(gè)偽代碼,其實(shí)就是一個(gè)發(fā)送 ajax 請(qǐng)求的方法
makeAjaxCall(url,function (response) {
it.next(reponse);
})
}
function* main() {
var result = yield request('http://some.url');
var resp = JSON.parse(result);
console.log(resp.value)
}
var it = main();
it.next();
- 第一次執(zhí)行
next
方法墓贿,發(fā)送ajax請(qǐng)求 - 在成功響應(yīng)的回調(diào)函數(shù)中茧泪,第二次執(zhí)行
next
方法,該方法將response
當(dāng)作上一次的返回值
控制流管理
這里只介紹最簡(jiǎn)單的一種方法
let steps = [step1Func, step2Func, step3Func];
function *iterateSteps(steps){
for (var i=0; i< steps.length; i++){
var step = steps[i];
yield step();
}
}
// 之后使用 for of 即可