Generattor函式的yield next雙向通訊
function *foo(x) {
var y = x * (yield "Hello"); // <-- yield一個值!
return y;
}
var it = foo( 6 );
var res = it.next(); // 第一個next(),並不傳入任何東西
res.value; // "Hello"
res = it.next( 7 ); // 向等待的yield傳入7
res.value; // 42
複製程式碼
為了較為清晰的理解這個過程,特意做了一個簡單的圖示,加深自己的記憶。
這個過程就好像是絲綢之路上的商人之間的貿易一樣,next從yield哪裡拿到一些東西同時也會給yield一些東西。多個迭代器
每次構建一個迭代器 ,實際上就隱式構建了生成器的一個例項,通過這個迭代器 來控制的是這個生成器例項
function *foo() {
var x = yield 2;
z++;
var y = yield (x * z);
console.log( x, y, z );
}
var z = 1;
var it1 = foo();
var it2 = foo();
var val1 = it1.next().value; // 2 <-- yield 2
var val2 = it2.next().value; // 2 <-- yield 2
val1 = it1.next( val2 * 10 ).value; // 40 <-- x:20, z:2
val2 = it2.next( val1 * 5 ).value; // 600 <-- x:200, z:3
it1.next( val2 / 2 ); // y:300
// 20 300 3
it2.next( val1 / 4 ); // y:10
// 200 10 3
複製程式碼
迭代器深入理解
如果一個物件包含一個可以迭代的迭代器(iterator),那麼這個物件就是一個iterable(可迭代)
從一個iterable中回去迭代器的方法:
// 以陣列為例
var a = [1,2,3]
var it = a[Symbol.iterator]();
it.next().value // 1
it.next().value // 2
it.next().value // 3
複製程式碼
每一個iterable中都有一個函式Symbol.iterator。呼叫這個函式會返回一個迭代器。
for of
迴圈可以自動呼叫Symbol.iterator函式來構建一個迭代器。
讓我們手動實現一個iterator(同時也是一個iterable)
var something = (function() {
let val
return {
// something物件是含有一個Symbol.iterator函式的,所以something是一個iterable;
// 這個Symbol.iterator函式返回的就是something本身,而它本身是有next()方法,所以實際上something本身也是一個迭代器(iterator)
[Symbol.iterator]: function() {return this},
next: function() {
val = val ? val + 2 : 1
return {
// 這裡一直返回false,所以這個迭代器沒有終點
done: false, value: val
}
}
}
})
複製程式碼
真正的iterator可以具有三個方法next, return, throw;我們自己實現的iterator可以按照需要省略return和throw
- return方法: 如果for...of迴圈提前退出(通常是因為出錯,或者有break語句)
var something = (function() {
let val
return {
// something物件是含有一個Symbol.iterator函式的,所以something是一個iterable;
// 這個Symbol.iterator函式返回的就是something本身,而它本身是有next()方法,所以實際上something本身也是一個迭代器(iterator)
[Symbol.iterator]: function() {return this},
next: function() {
val = val ? val + 2 : 1
return {
// 這裡一直返回false,所以這個迭代器沒有終點
done: false, value: val
}
},
return() {
console.log('return 被觸發')
return {done: true}
}
}
})
for(let i of something()) {
if(i > 10) {break}
console.log(i)
}
// 1
// 2
// 3
// 4
// 5
// 6
// 7
// 8
// 9
// return 被觸發
複製程式碼
-
throw方法主要是配合生成器函式一起使用,一般的iterator用不到這個方法。 next()、throw()、return()在生成器函式中這三個方法本質上是一樣的, 都是讓Generator函式恢復執行,並且使用不同的語句替換yield表示式。
next()是將一個值傳遞給yield
const g = function* (x, y) { let result = yield x + y; return result; }; const gen = g(1, 2); gen.next(); // Object {value: 3, done: false} gen.next(1); // Object {value: 1, done: true} // 相當於將 let result = yield x + y // 替換成 let result = 1; 複製程式碼
throw()是將一個錯誤傳遞給yield
gen.throw(new Error('出錯了')); // Uncaught Error: 出錯了 // 相當於將 let result = yield x + y // 替換成 let result = throw(new Error('出錯了')); 複製程式碼
return()是將return語句傳遞給yield
gen.return(2); // Object {value: 2, done: true} // 相當於將 let result = yield x + y // 替換成 let result = return 2; 複製程式碼
生成器(Generator) vs 迭代器
生成器並不是一個iterater,它執行的結果才是一個iterator
function * foo() {}
// it是一個itreator
var it = foo()
複製程式碼
所以我們嘗試用生成器實現上面的something:
function *something() {
var val
// 在生成器中使用while..true並沒有問題
while(true) {
val = val ? val + 2 : 1
yield val
}
}
複製程式碼
這個實現方式跟我們之前的閉包方式相比更加簡潔,不需要閉包來保持變數狀態了。
for (let i of something()) {
if(i > 10){break}
console.log(i)
}
複製程式碼
前面我們說過something()生成的是一個iterator,而for...of需要的是一個iterable; 實際上生成器something()生成的iterator同時也是一個iterable,其內部的Symbol.iterator實際上也是類似於return this 的做法。
生成器與Promise的完美結合
生成器函式給了我們一個看似同步的流程控制程式碼配合Promise(可信任可組合)的組合可能是js新世界中最美妙的事情。
所以,你應該想到了,ES78(ES2017)中提供的async/await正式這兩者完美結合的語法級支援。 實際上,asyn函式不過是Generator(生成器)函式的一個語法糖
// 手動結合Generator和Promise
// 這裡不直接定義promise而是通過foo返回是因為promise在定義的時候就會執行
function foo() {
return new Promise(function(resolve, reject) {
resolve(10)
})
}
function *gen() {
try{
let text = yield foo()
console.log(text)
} catch(err) {
console.log(err)
}
}
let it = gen()
let p =it.next().value
p.then(
function(res) {
it.next(res)
},
function(err) {
it.throw(err)
}
)
// 10
複製程式碼
可以看到手動結合Promise和Generator是多麼的繁瑣;雖然帶來了同步的流程和可信任可組合的Promsie,這也不是我們願意看到的。
看看async/await如何實現上述過程
function foo() {
return new Promise(function(resolve, reject) {
resolve(10)
})
}
async function aw() {
let text = await foo()
console.log(text)
}
aw() // 10
複製程式碼
是不是清爽了很多,async/await實際上是把Generator的啟動步驟和Promsie內部的next()實現細節隱藏到語法糖中,留給我們一個清爽的世界。如果希望瞭解實現細節,我們不放手動模仿以下這個語法糖函式
示例來自‘你不知道的javascript(中卷)’
function run(gen) {
var args = [].slice.call( arguments, 1), it;
// 在當前上下文中初始化生成器
it = gen.apply( this, args );
// 返回一個promise用於生成器完成
return Promise.resolve()
.then( function handleNext(value){
// 對下一個yield出的值執行
var next = it.next( value );
return (function handleResult(next){
// 生成器執行完畢了嗎?
if (next.done) {
return next.value;
}
// 否則繼續執行
else {
return Promise.resolve( next.value )
.then(
// 成功就恢復非同步迴圈,把決議的值發回生成器
handleNext,
// 如果value是被拒絕的 promise,
// 就把錯誤傳回生成器進行出錯處理
function handleErr(err) {
return Promise.resolve(
it.throw( err )
)
.then( handleResult );
}
);
}
})(next);
} );
}
複製程式碼