入門
簡單來說,用法如下:
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); }