會注意到 Generator 主要是因為我看到了很特別的寫法
function *
& yield
於是這趟初探 Generator 之旅就開始囉
前言
什麼是 Generator ? 可以想像成它是個迭代產生器
不用去特意實作迭代器的細節實作
直接使用語法就可以製作出不俗的迭代器
基本語法及概念
需要再函數上標註 *
宣告成為產生器函數
一開始呼叫時會返回個 Generator 物件
再來就是利用 Generator 物件的 next()
方法執行到函數內的 yield
並返回一個物件
// * 宣告 temp 為 Generator 函式
function* temp() {
console.log('temp start!');
yield 'go';
}
// 生成 Generator
var g = temp();
// 印出 temp start! 執行至 yield 之後返回
var result = g.next();
如果要檢視傳回的結果, value 屬性是 yield expression 的執行結果
// 印出 go
console.log(result.value);
done 屬性用來判斷 Generator 是否迭代完
由於代碼中 yield
僅只一次, 所以再次執行 next()
所取得的物件 done 屬性將為 true
result = g.next();
// 印出 true
console.log(result.done);
Generator 也可接收來自外部傳遞的參數
只要透過 next()
將參數傳遞進去即可
// * 宣告 temp 為 Generator 函式
function* temp() {
console.log('temp start!');
const outParam = yield 'go';
console.log(outParam);
}
// 生成 Generator
var g = temp();
// 印出 temp start!
var result = g.next();
// 印出 go
console.log(result.value);
// 印出 hello generator!
g.next('hello generator!');
Generator 物件還有個 throw
方法用來拋出例外
// * 宣告 temp 為 Generator 函式
function* temp() {
console.log('temp start!');
const outParam = yield 'go';
console.log(outParam);
}
// 生成 Generator
var g = temp();
// 拋出一個例外,錯誤訊息是 'error'
g.throw('error');
用途
在遇到一連串的先後順序的的非同步操作需求
當回傳的結果又彼此相依, 需要從一個 callback 呼叫下一個非同步操作
以下使用 fetch 來當作範例
var fetch = require('node-fetch');
function withoutYield(handle) {
const user_url = `https://api.github.com/users/${handle}`;
const repo_url = `https://api.github.com/users/${handle}/repos`;
fetch(user_url)
.then(response => response.json())
.then(user => {
// 先取得使用者
console.log(user.name);
console.log(user.location);
fetch(repo_url)
.then(response => response.json())
.then(repos => {
// 取得 repository
console.log(`${repos.length} repos`);
})
});
}
withoutYield('l7960261');
可以看到光是兩層再加上邏輯就已經頗複雜
當然以 Promise 本身就可以來解決 callback hell 的問題了
不過有了 Generator 可以來管理函數回傳結果並迭代到下一次的非同步操作
co 是一個簡單的流程管理函式取自初探 ES6(3)Generator by fillano | CodeData文章的範例
var fetch = require('node-fetch');
function getUsers(goToNext) {
const handle = 'l7960261';
const user_url = `https://api.github.com/users/${handle}`;
return fetch(user_url)
.then(response => response.json())
.then(user => {
console.log(user.name);
console.log(user.location);
goToNext(null, 'Get user done!');
});
}
function getRepos(goToNext) {
const handle = 'l7960261';
const repo_url = `https://api.github.com/users/${handle}/repos`;
return fetch(repo_url)
.then(response => response.json())
.then(repos => {
console.log(`${repos.length} repos`);
goToNext(null, 'Get repos done!');
});
}
function* withYield() {
const r1 = yield getUsers;
console.log(r1); // 印出 Get user done!
const r2 = yield getRepos;
console.log(r2); // 印出 Get repos done!
}
function co(gen) {
var g = gen();
function next(err, data) {
var res;
if (err) {
return g.throw(err);
} else {
res = g.next(data);
}
if (!res.done) {
res.value(next)
}
}
next();
}
co(withYield);
小結
以上是這幾天接觸 Generator 的刻意練習
我認為 Generator 語法一開始真的不好懂而且也不容易聯想, 無法實際的很好的運用在團隊開發中
比起上一篇的 JavaScript ES7 Async Await
我更喜歡 async/await 的寫法更加直覺、簡潔
相關連結
初探 ES6(3)Generator by fillano | CodeData