入門
簡單來說,用法如下:
function* fn() {
console.log(1);
//暫停!
yield;
//調用next方法繼續執行
console.log(2);
}
var iter = fn();
iter.next(); //1
iter.next(); //2
1、函數生成器特點是函數名前面有一個‘*’
2、通過調用函數生成一個控制器
3、調用next()方法開始執行函數
4、遇到yield函數將暫停
5、再次調用next()繼續執行函數
消息傳遞
除了暫停和繼續執行外,生成器同時支持傳值。
用法如下:
function* fn() {
var a = yield 'hello';
yield;
console.log(a);
}
var iter = fn();
var res = iter.next();
console.log(res.value); //hello
iter.next(2);
iter.next(); //2
可以看到,yield后面有一個字符串,在第一次調用next時,暫停在這里且返回給了iter.next()。
而暫停的地方是一個賦值語句,需要一個變量給a,於是next()方法中傳了一個參數2替換了yield,最后打印a得到了2。
異步應用
通過yield來實現異步控制流程:
function fn(a, b) {
//假設這是一個ajax請求
ajax('url' + a + b, function(data) {
//數據請求到會執行it.next
it.next(data);
});
}
//這里是函數生成器
function* g() {
//當異步操作完畢yield會得到值
//這里會自動繼續執行
var text = yield fn(a, b);
console.log(text);
}
var it = g();
it.next();
這里做了簡化處理,忽略了一些錯誤處理。
確實很巧妙,通過回調函數來繼續執行函數生成器,然后得到數據。
然而,直接在回調里拿數據不行么。書上講,這樣異步操作符合大腦思考模式,函數的執行看起來‘同步’了。
yield+promise
重點來了。
先回憶之前promise對異步的實現:
function request(url) {
return new Promise(function(resolve, reject) {
//ajax異步請求完成會調用resolve決議
ajax(url, resolve);
});
}
request('url').then(function(res) {
console.log(res);
})
流程大概是調用函數傳入url,由於會立即決議,觸發ajax請求函數。異步請求完調用調用回調函數,也就是resolve,然后根據返回的resolve調用then方法獲取數據。
現在將yield與promise綜合在一起:
function foo(x) {
return request('url' + x);
}
//等待promise決議值返回
function* fn() {
var text = yield foo(1);
}
var it = fn();
//返回一個promise
var p = it.next().value;
//對promise處理
p.then(function(text) {
//這里繼續執行生成器
it.next(text);
})
封裝
可以將上面的yield+promise進行封裝,得到下面的函數:
function run(gen) {
//獲取除了生成器本身以外的參數
var args = [].slice.call(arguments, 1),
it;
//it = main()
it = gen.apply(this, args);
return Promise.resolve().then(function handleNext(value) {
//第一次啟動無value
var next = it.next(value);
return (function handleResult(next) {
//執行完畢返回
if (next.done) {
return next.value;
} else {
//如果還有就決議next.value傳給handleNext
return Promise.resolve(next.value).then(handleNext, function(err) {});
}
})(next);
});
}
//這是一個函數生成器
function* main() {
//...
};
//該調用會自動異步運行直到結束
run(main);
如果有兩個異步操作,獲取到返回的兩個數據后,再進行第三個異步操作,可以這么做:
function foo() {
var p1 = request('url1'),
p2 = request('url2');
//每一個request異步請求完成后會自動解除yield
var r1 = yield p1,
r2 = yield p2;
var r3 = yield request('url3' + r1 + r2);
console.log(r3);
}

