embryo

エンジニアの備忘録

Generatorと非同期処理

今日はes6の仕様であるGeneratorについてです。

es6でカスタムイテレータを作る (Iterator, Iterable, Generator) - embryo

で簡単に紹介しましたが、今回はGeneratorで非同期処理を扱ってみたいと思います。

co で非同期処理

GitHub - tj/co: The ultimate generator based flow-control goodness for nodejs (supports thunks, promises, etc)

coとはGeneratorを利用して非同期処理を同期的に記述することを目的としたライブラリです。 下記のように一連の非同期処理を同期的に実行できるため、処理をシンプルに記述できる他、同一スコープ内で非同期処理の結果を共有することも可能になります。

function asyncTask(value) {
     return new Promise(resolve => {
         setTimeout(() => {
              resolve(value);
         }, 1000)
     });
}

co(function *() {
     let result = yield asyncTask(100)
     console.log(result);
})

今回はこの実装を参考にしつつ、簡易な方法で実装していきます。

普通にGeneratorを使う

まずGeneratorを素直に使用する方法です。Generatorはnextを持っているため、このnextを非同期処理の完了時に呼び出すことで 非同期処理を順次実行することができそうです。

gist.github.com

この場合Generatorを処理の外で実行する必要がある他、Generatorを仕様側が知っている必要があります。そこで次の実装ではGeneratorを意識せずとも実行できるように書いてみます。

Generatorを内部で実行する

gist.github.com

Generatorを内部で実行する方法です。Generatorの引数にcallbackを引数として渡しており、実行側では任意のタイミングでcallbackを実行すればよいため、generatorを意識せずに使用できます。しかしこの場合仕様側はコールバックを順次呼びだす必要があるため、本質的には非同期処理的な書き方と言えます。

Promiseを使用する

gist.github.com

iteratorのresult.doneがtrueを返すまで、PromiseのonFullfilledが呼ばれる度にgenerator#nextを再帰的に呼び出します。仕様側はGeneratorやコールバックを意識すること無く、かつ同期的に非同期処理を実行することが出来るようになりました。 前述したcoも仕組みとしては大体同じです。(coではエラーハンドリングなども考慮されていますが今回は説明のため簡易の実装にとどめています)

おまけ

ちなみに現在仕様策定中の async, await を用いれば coのようなライブラリに頼らずとも標準で上記のような処理を書くことが出来ます。

https://tc39.github.io/ecmascript-asyncawait/

async function () {
     console.log(await new Promise().resolve(100));
     console.log(await new Promise().resolve(200));
     console.log(“complete”);
}

/*
100
200
complete
*/

参考