JavaScript ES6 Generator

會注意到 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 的寫法更加直覺、簡潔

相關連結

TypeScript 2.3 · TypeScript

初探 ES6(3)Generator by fillano | CodeData

What Is This Thing Called Generators?

Callbacks vs Coroutines – TJ Holowaychuk – Medium