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');