yield, Promise, coの話

ES2015ではPromiseを使って非同期処理が書けるわけですが、yieldや、Node.jsのパッケージcoと連携させるとうまく使えます。

基本事項

Promise

Promiseは以下のようにして使います。

const p = new Promise(function(success, failed) {
  // 処理本体
});

p.then(function(res) {
  // 成功時の処理
}).catch(function(res) {
  // 失敗時の処理
});

これでコールバック処理の分かりづらい表現が回避できます。

yield

yieldはジェネレーターです。関数を元に、実行を途中で一時停止して値を戻し、また値を与えて復帰することができるオブジェクト(イテレーター)を生成することができます。

const gen = function* (mes) {
  const mes2 = yield mes + mes;
  return mes+mes+mes2;
}

const it = gen('hoge');
console.log(it.next().value); // hogehogeと出力される
console.log(it.next('fugafuga').value); // hogehogefugafugaと出力される
console.log(it.next()); // Object { value: undefined, done: true} が出力される

以上の例でわかるように

  • yieldがあると、yieldの右側の式を評価し、その値を返却して処理を一時停止する。
    上の例だと2, 7行目に相当します。
  • yield後にnextで処理を再開する際、nextに引数を与えると、元の処理のyieldに相当する部分でその値が渡される。
    上の例だと2, 8行目に相当します。
  • 残された処理がなくなると、{ value: undefined, done: true }が返ってくるようになる
    上の例だと、9行目。ただし、本当は上の例では2回めのnextの時点でもdone: trueになっている。

という仕組みになります。

yieldを利用した非同期・逐次実行的処理

yieldの仕組みを応用すると、非同期・逐次実行的な処理の記述が楽になります。以下の通り。

function twice(mes, next) { setTimeout(() => next(mes + mes), 1000); }
function join(mes1, mes2, next) { setTimeout(() => next(mes1 + mes2), 10); }

function* gen(mes, next) {
  const mes2 = yield twice(mes, next);
  console.log(mes2);
  const mes3 = yield join(mes+mes, mes2, next);
  console.log(mes3);
}

const it = gen('hoge', (v => it.next(v)));
it.next(); // hogehoge, hogehogehogehogeと順に出力される

要するに、yieldの右辺で非同期処理を起動した後、その非同期処理のコールバックからイテレーターのnextを叩けばよいというわけです。

Promise, coでもっと簡単に

これらの処理をまとめてやってくれるのがcoです。Promiseとcoを利用すると、さらに以下のように書き換えられることになります。

function twice(mes) {
  return new Promise((success) => {
    setTimeout(() => success(mes + mes), 1000);
  });
}
function join(mes1, mes2) {
  return new Promise((success) => {
    setTimeout(() => success(mes1 + mes2), 10);
  });
}

function* gen(mes) {
  const mes2 = yield twice(mes);
  console.log(mes2);
  const mes3 = yield join(mes+mes, mes2);
  console.log(mes3);
}

const co = require('co');
const wrapped = co.wrap(gen);
wrapped('hoge');

参考