Ruby で ES7 の async/await を超絶簡単に実装する

## Introduction
突然ですが、皆さん async/await は知っていますか！？
async/await といえば ES7 での仕様策定に向けて議論が進められている ECMAScript の新機能で、非同期処理を同期処理っぽく書く事ができます（ルーツは C# のasync/await らしいです）。

async/await について理解を深める為に、実際に（現在提案されてる仕様での）使い方を見てみましょう。
以下は http://wiki.ecmascript.org/doku.php?id=strawman:async_functions からの抜粋です。

```javascript
  1 var request = require('./request.js');
  2 var headers = { 'User-Agent': 'lukehoban', 'Authorization': 'token 665021d813ad67942206d94c47d7947716d27f66' };
  3
  4 // Promise-returning asynchronous function
  5 async function getCollaboratorImages(full_name) {
  6   // any exceptions thrown here will propogate into try/catch in callers - same as synchronous
  7   var url = 'https://api.github.com/repos/' + full_name + '/collaborators';
  8   // await a promise-returning async HTTP GET - same as synchronous
  9   var [response, body] = await request({url: url, headers: headers});
 10   return JSON.parse(body).map(function(collab) {
 11     return collab.avatar_url;
 12   });
 13 }
```

ちょっと複雑すぎる例になってますが、注目すべきは **9行目** の `var [response, body] = await request({url: url, headers: headers});` です。この行では、`request({url: url, headers: headers})` で http リクエストを投げて、その結果を `response, body` に代入しています。さらに続く10行目以降で、 `body` が関数の返り値の生成に使われています。
このコードだけを見ていると、9行目では同期的に http request が投げられ、その response が返ってくるのを待って（Network IO でブロックして）、 `resopnse, body` の代入が行われている様に見えます。しかし、実際にはそうではありません。実は、上記のコードは以下の様なコードと等価になります。

```javascript
// Promise版
  1 var request = require('./request.js');
  2 var headers = { 'User-Agent': 'lukehoban', 'Authorization': 'token 665021d813ad67942206d94c47d7947716d27f66' };
  3
  4 function getCollaboratorImages(full_name) {
  5   var url = 'https://api.github.com/repos/' + full_name + '/collaborators';
  6   return request({url: url, headers: headers}).then(function(value) {
  7     var [response, body] = value;
  8     JSON.parse(body).map(function(collab) {
  9       return collab.avatar_url;
 10     });
 11   })
 12 }
```

Promise が使われていて、見慣れたコードになっていますね。ポイントは `getCollaboratorImages` の返り値が `Promise` オブジェクトになっている事、また `request({url: url, headers: headers})` が返す `Promise` オブジェクトに対しては `then` で続きの処理を書いている事です。`then` によって callback の処理を登録している為、完全に非同期に実行されるコードとなっています。

Promise を使ったコードと async/await を使ったコードが、なぜ等価となるのでしょうか？
鍵になるのは、ES6で導入された `Generator` という機能です。

## ES6 でも Generator と Promise を使うと async/await が実装できる
実は、Generator と Promise を組み合わせる事で async/await （と同等の機能を果たす関数）を実装する事が出来ます。
これに関しては、 [ES6 Generatorを使ってasync/awaitを実装するメモ](http://blog.h13i32maru.jp/entry/2015/01/02/225457) が詳しいので興味が湧いた人は読んでみてください。

上記記事で注目すべきなのは、Generator をコルーチンとして使っていて、Promiseと組み合わせていることです。この2つが async/await を実装する為に必要なパーツとなります。

コルーチンは、雑に説明すると「処理の中断や再開が可能な手続き処理」を指します。有名どころだと、Lua や C# が言語レベルでコルーチンをサポートしています。コルーチンの呼び出し側とコルーチン側で制御を行き来する事が出来て、その際に値の受け渡しもできるのが特徴です。

逆に言えば、コルーチンと Promise があれば async/await は実装できるわけです。さて、ここで本題にいきたいと思いますが、 Ruby で コルーチンといえば `Fiber` ですよね？
という事で、`Fiber` を使って、簡単に async/await を実装してみました。

## Ruby で Fiber を使って async/await を実装する
それでは、Ruby で async/await を実装してみたいと思います。

今回はサンプル実装なので、例外の発生は考慮せず、`Promise` も以下の様な `initialiize` と `then` だけが定義された簡単なものを考えます。

```ruby
  1 class Promise
  2   def initialize(callback)
  3     @callback = callback
  4   end
  5
  6   def then(resolve = ->() {}, reject = ->() {})
  7     @callback.call(resolve, reject)
  8   end
  9 end
```

これは、例えば以下の様に使います。

```ruby
  1 require 'eventmachine'
  2
  3 def sleep(sec)
  4   Promise.new(->(resolve, reject) {
  5     EM.add_timer(sec) do
  6       resolve.call('you sleep ' + sec.to_s)
  7     end
  8   })
  9 end
 10
 11 EM.run do
 12   sleep(0.3).then(->(value) {
 13     p value
 14   })
 15   p 'start'
 16 end
 17 #=> "start"
 18 #=> "you sleep 0.3"
```

`sleep` メソッドの 返り値である `Promise` オブジェクトに対して、`then` で続きの処理（callback）を渡しています。`sleep` の動作が「渡された数字の秒数だけ待ってから、`then` で登録された callback を実行する」というものになっている為、上記のコードを事項すると15行目の `p 'start'` で `"start"` が出力されてから、13行目の `p value` で `"you sleep 0.3"` が出力されます（尚、即時 return してから登録した callback を実行するという動作を実現する為に `EventMachine` を使っています。`EM.run` に渡したブロックの中では様々な non-blocking api が使えて、例えば上記の例では JS の `setTimeout` 的な振る舞いをする `EM.add_timer` を使っています）。

さらに `Promise` を返す別のメソッドとして以下の様な `decorate_message` を定義して、sleep に続けて使う `func` を考えます（僕が雑に定義した `Promise` の `then` メソッドは `Promise` オブジェクトを返さない為、chaining はできてませんが気にしないでください）

```ruby
  1 def decorate_message(message)
  2   Promise.new(->(resolve, reject) {
  3     http = EM::HttpRequest.new('http://google.com/').get
  4     http.callback {
  5       resolve.call(message + ': ' + http.response[0..100])
  6     }
  7   })
  8 end
```

```ruby
# use Promise
  1 def func(time)
  2   sleep(time).then(->(message) {
  3     p message
  4     decorate_message(message).then(->(decorated_message) {
  5       p decorated_message
  6     })
  7   })
  8 end
  9
 10 EM.run do
 11   func(0.3)
 12   p 'start'
 13 end
 14 #=> "start"
 15 #=> "you sleep 0.3"
 16 #=> "you sleep 0.3: <HTML><HEAD><meta http-equiv=\"content-type\" content=\"text/html;charset=utf-8\">\n<TITLE>302 Moved</TITL"
```

この `func` を今から定義する `async`, `awai` メソッドをつかって書き換えると、以下の様になります。`sleep` や `decorateなんだかそれっぽく見えますね！！

```ruby
# use async/await
  1 async :asyncFunc do |time|
  2   message = await sleep(time)
  3   p message
  4   decorate_message = await decorate_message(message)
  5   p decorate_message
  6 end
  7
  8 EM.run do
  9   asyncFunc(0.3)
 10   p 'start'
 11 end
 12 #=> "start"
 13 #=> "you sleep 0.3"
 14 #=> "you sleep 0.3: <HTML><HEAD><meta http-equiv=\"content-type\" content=\"text/html;charset=utf-8\">\n<TITLE>302 Moved</TITL"
```

次に、ここで使った `async` メソッドと `await` メソッドの実装を見てみたいと思います。

### Fiber を使った async/await の実装
一番シンプルな例として、 `Promise` の `then` で `resolve` だけが指定されるケースを考える（かつ例外の発生も考慮しない）と、以下のコードで async/await が実現できます。たった19行の短いコードです。

```ruby
# implementation of async/await
  1 def async_internal(fiber)
  2   chain = ->(result) {
  3     return if result.class != Promise
  4     result.then(->(val) {
  5       chain.call(fiber.resume(val))
  6     })
  7   }
  8   chain.call(fiber.resume)
  9 end
 10
 11 def async(method_name, &block)
 12   define_method method_name, ->(*args) {
 13     async_internal(Fiber.new { block.call(*args) })
 14   }
 15 end
 16
 17 def await(promise)
 18   Fiber.yield promise
 19 end
```

注目するポイントは **16行目** の `Fiber.new` と、 **5行目** の `fiber.resume` 、 **21行目** の `Fiber.yield` です。`Fiber` はコルーチンの機能を提供するためのモジュールで、 `Fiber.new` にコルーチンにしたい処理をブロックで渡して使います。

`Fiber` の挙動は、以下のコードを見るとイメージしやすいかもしれません。`Fiber.new` に渡した処理は、生成した `fiber` に対して `resume` を呼び出す事で実行されます。以下の例では、 **13行目** で `fiber.resume(i)` を実行しており、このタイミングで `"init fiber"` が出力されます。ただし、**5行目** で `Fiber.yield` が呼ばれると、処理がストップして制御は **13行目** に戻ります。この様に、コルーチンの「処理を途中で抜ける」機能は `Fiber.yield` によって実現されます。

```ruby
# fiber example
  1 fiber = Fiber.new do
  2   p 'init fiber'
  3   n = 0
  4   loop do
  5     result = Fiber.yield(n)
  6     p "after yiled: #{result}"
  7     n += 1
  8   end
  9 end
 10
 11 i = 10
 12 4.times do
 13   result = fiber.resume(i)
 14   p "after resume: #{result}"
 15   i += 1
 16 end
 17 # >> "init fiber"
 18 # >> "after resume: 0"
 19 # >> "after yiled: 11"
 20 # >> "after resume: 1"
 21 # >> "after yiled: 12"
 22 # >> "after resume: 2"
 23 # >> "after yiled: 13"
 24 # >> "after resume: 3"
```

`Fiber.yield` には引数を渡す事が出来て、渡した引数は `fiber.resume` の返り値となります。逆に、`fiber.resume` に引数を渡す事も出来て、この場合は `Fiber.yield` から処理を再開する際にその返り値として使う事ができます。返り値の関係は少しややこしいですが、あくまで「`Fiber.yield` が通常の `return` に相当するもの（よって `fiber.resume` の返り値になる）」、「`fiber.resume` の2回目以降の呼び出しは処理の再開を意味していて、再開時に初期値を渡す事ができる」みたいに考えておくと良いと思います。（よく見ると `fiber.resume` の最初の引数は使われていない事がわかると思います）

`Fiber` を使う事で、 `Fiber.yield` と `fiber.resume` で制御を行ったり来たりしながら協調して作業を進める事ができます。

ここで `async`, `await` の実装に戻ると、`await` はただ `Fiber.yield` を実行しているだけ、また `async` も実行したい処理をブロックとして受け取って `Fiber` オブジェクトを作り出し、`async_internal` に渡しているだけである事がわかります。

`async_internal` がメインのロジックですが、ここもそれほど複雑な事はしていません。`chain` という名前で再帰的に実行される `Proc` オブジェクトを作り、メソッドの最後に `chain.call` を呼ぶことで再帰処理を開始しています。

`chain.call` の引数に `fiber.resume` を書いているのがポイントで、`fiber.resume` によってコルーチンの処理が開始され、`Fiber.yield`（つまり `await`）が呼ばれるまで処理が続きます。`await` に出くわすと`fiber.resume` を呼び出した先に制御が返りますが、その際に返り値は `Promise` となっていて、それが `chain` の引数になります。`chain` の中では引数に対して `then` で続きの処理を書いて、再び `fiber.resume` を呼び出します。この時、`fiber.resume` に初期値として渡した値がコルーチンの処理の再開時に `await` の返り値として取得できます。

以上の様なフローによって、`async` と `await` を使う事で同期的処理の様に書きながらも非同期処理を実現する事ができました。[^1]

## まとめ
- ES7 での仕様策定が進められている async/await を使うと、非同期処理を同期処理っぽい syntax で記述できる
- async/await は、Generator をコルーチンとして利用し、さらに Promise と組み合わせる事で実現できる
- コルーチンは、 JavaScript 以外にも Ruby や Lua など様々な言語で提供されている機能である為、様々な言語で async/await は実現できる（と思う）

## 参考資料
- [strawman:async_functions](http://wiki.ecmascript.org/doku.php?id=strawman:async_functions)
- [ES7 の Async/Await を使ってみた](http://qiita.com/mohayonao/items/435665065d997a4cc50c)
- [ES6 Generatorを使ってasync/awaitを実装するメモ](http://blog.h13i32maru.jp/entry/2015/01/02/225457)

[^1]: Ruby 版 `async` は `Promise` を返さないとか、`Promise` がそもそも chaining できないとか細かな差異はいろいろありますが、その辺は API をどうするかという問題なので、適宜書き換えれば調整はできると思ってます。