ReactPHP？時代はRecoilPHPだ！

# 時代変遷

## JavaScript では

JavaScriptで非同期処理の時代変遷と言えば，こんな感じでやってきたと思います．

(例)
1秒後に`+1`した値をコールバック引数に渡す関数を使って，以下の2つの流れを同時に処理し，両方終わった段階で「All done !!」と表示することにします．

- 1→2→3
- 4→5→6

### callback (旧石器時代)

```js
function plusOneLater(a, callback) {
    setTimeout(function () {
        callback(a + 1);
    }, 1000);
}

var active = 2;
plusOneLater(0, function (x) {
    console.log(x);
    plusOneLater(x, function (y) {
        console.log(y);
        plusOneLater(y, function (z) {
            console.log(z);
            if (--active <= 0) {
                console.log('All done !!');
            }
        });
    });
});
plusOneLater(3, function (x) {
    console.log(x);
    plusOneLater(x, function (y) {
        console.log(y);
        plusOneLater(y, function (z) {
            console.log(z);
            if (--active <= 0) {
                console.log('All done !!');
            }
        });
    });
});
```

### Promise (ES6)

```js
function plusOneLater(a) {
    return new Promise(function (resolve) {
        setTimeout(function () {
            resolve(a + 1);
        }, 1000);
    });
}

Promise.all([
    plusOneLater(0).then((x) => { console.log(x); return plusOneLater(x); })
                   .then((y) => { console.log(y); return plusOneLater(y); })
                   .then((z) => { console.log(z); }),
    plusOneLater(3).then((x) => { console.log(x); return plusOneLater(x); })
                   .then((y) => { console.log(y); return plusOneLater(y); })
                   .then((z) => { console.log(z); }),
]).then(() => console.log('All done !!'));
```

### Promise + Generator + co (ES6)

```js
var co = require('co');

function plusOneLater(a) {
    return new Promise(function (resolve) {
        setTimeout(function () {
            resolve(a + 1);
        }, 1000);
    });
}

co(function *() {
    yield [
        function *() {
            var x = yield plusOneLater(0);
            console.log(x);
            var y = yield plusOneLater(x);
            console.log(y);
            var z = yield plusOneLater(y);
            console.log(z);
        },
        function *() {
            var x = yield plusOneLater(3);
            console.log(x);
            var y = yield plusOneLater(x);
            console.log(y);
            var z = yield plusOneLater(y);
            console.log(z);
        },
    ];
    console.log('All done !!');
});
```

(ES7のasync/awaitは省略)

## PHP では

### ReactPHP

PHPでも数年前から**[ReactPHP](http://reactphp.org/)**というものが登場し，言語ネイティブでのイベントループは実現できませんが，工夫をこらせばJavaScriptっぽい書き方も出来るようになってきました．

```php:ReactPHP&#32;(PHP5,&#32;PHP7)
<?php

require 'vendor/autoload.php';
$GLOBALS['loop'] = React\EventLoop\Factory::create();

function plusOneLater($a) {
    $deferred = new React\Promise\Deferred();
    $GLOBALS['loop']->addTimer(1, function () use ($deferred, $a) {
        $deferred->resolve($a + 1);
    });
    return $deferred->promise();
}

React\Promise\all([
    plusOneLater(0)->then(function ($x) { echo "$x\n"; return plusOneLater($x); })
                   ->then(function ($y) { echo "$y\n"; return plusOneLater($y); })
                   ->then(function ($z) { echo "$z\n"; }),
    plusOneLater(3)->then(function ($x) { echo "$x\n"; return plusOneLater($x); })
                   ->then(function ($y) { echo "$y\n"; return plusOneLater($y); })
                   ->then(function ($z) { echo "$z\n"; }),
])->then(function () { echo "All done !!\n"; });

$GLOBALS['loop']->run();
```

### RecoilPHP

最近 **[RecoilPHP](https://github.com/recoilphp/recoil)** というものも出てきたようで，ジェネレータを使った書き方も出来るようになってました！以下の点で優位性があると思います．

- `function`の数が少なくて済む
- ReactPHPではグローバル変数としていたイベントループが，RecoilPHPではクラス内部に`static`変数としてあらかじめ用意されている (推測)

```php:PHP7.0+
<?php

require 'vendor/autoload.php';

function plusOneLater($a) {
    yield Recoil\Recoil::sleep(1);
    return $a + 1; // PHP7限定
}

Recoil\Recoil::run(function () {
    yield Recoil\Recoil::all([
        (function () {
            $x = yield plusOneLater(0); // PHP7限定
            echo "$x\n";
            $y = yield plusOneLater($x); // PHP7限定
            echo "$y\n";
            $z = yield plusOneLater($y); // PHP7限定
            echo "$z\n";
        })(), // PHP7限定
        (function () {
            $x = yield plusOneLater(3); // PHP7限定
            echo "$x\n";
            $y = yield plusOneLater($x); // PHP7限定
            echo "$y\n";
            $z = yield plusOneLater($y); // PHP7限定
            echo "$z\n";
        })(), // PHP7限定
    ]);
    echo "All done !!\n";
});
```

ただし，

- JavaScriptの`co`のように，**ジェネレータ関数そのものの`yield`には(一番外側を除いて)対応してくれない**ことに注意してください．**クロージャの`()`による即時実行はPHP7でしか出来ません**．
- PHP5ではジェネレータ関数で**値つきの`return`が出来ません**．
- PHP5では`yield`の評価優先順位がおかしなことになってるので，**代入が伴う場合括弧が必要**です．

このため，PHP5だと少し残念なことになります（笑）

```php:PHP5.5+
<?php

require 'vendor/autoload.php';

function plusOneLater($a) {
    yield Recoil\Recoil::sleep(1);
    yield Recoil\Recoil::return_($a + 1); // ダサい
}

Recoil\Recoil::run(function () {
    yield Recoil\Recoil::all([
        call_user_func(function () { // ダサい
            $x = (yield plusOneLater(0)); // ダサい
            echo "$x\n";
            $y = (yield plusOneLater($x)); // ダサい
            echo "$y\n";
            $z = (yield plusOneLater($y)); // ダサい
            echo "$z\n";
        }),
        call_user_func(function () { // ダサい
            $x = (yield plusOneLater(3)); // ダサい
            echo "$x\n";
            $y = (yield plusOneLater($x)); // ダサい
            echo "$y\n";
            $z = (yield plusOneLater($y)); // ダサい
            echo "$z\n";
        }),
    ]);
    echo "All done !!\n";
});
```

# まとめ

ReactPHPとの親和性も高くて，なかなか面白いライブラリだと思うので，機会があれば使ってみては如何でしょうか．ReactPHP単体よりはRecoilPHPを織り交ぜた方が断然使いやすいと思います．

関連投稿: [「PHPでクロージャのuse地獄を回避する裏ワザ」](http://qiita.com/mpyw/items/f5f29523dd5dce953f6b)

# 2016/08/04 追記: [mpyw/co](https://github.com/mpyw/co) を使った場合

このライブラリは

- cURLリソースを用いたHTTP通信
- コルーチンごとの擬似スリープ

しか取り扱えませんが，いくらか RecoilPHP よりもシンプルに書くことができます．

```php:PHP7.0+
<?php

require 'vendor/autoload.php';

use mpyw\Co\Co;

function plusOneLater($a) {
    yield Co::DELAY => 1; 
    return $a + 1; // PHP7限定
}

Co::wait(function () {
    yield [
        function () {
            $x = yield plusOneLater(0); // PHP7限定
            echo "$x\n";
            $y = yield plusOneLater($x); // PHP7限定
            echo "$y\n";
            $z = yield plusOneLater($y); // PHP7限定
            echo "$z\n";
        },
        function () {
            $x = yield plusOneLater(3); // PHP7限定
            echo "$x\n";
            $y = yield plusOneLater($x); // PHP7限定
            echo "$y\n";
            $z = yield plusOneLater($y); // PHP7限定
            echo "$z\n";
        },
    ];
    echo "All done !!\n";
});
```

```php:PHP5.5+
<?php

require 'vendor/autoload.php';

use mpyw\Co\Co;

function plusOneLater($a) {
    yield Co::DELAY => 1; 
    yield CO::RETURN_WITH => $a + 1;
}

Co::wait(function () {
    yield [
        function () {
            $x = (yield plusOneLater(0));
            echo "$x\n";
            $y = (yield plusOneLater($x));
            echo "$y\n";
            $z = (yield plusOneLater($y));
            echo "$z\n";
        },
        function () {
            $x = (yield plusOneLater(3));
            echo "$x\n";
            $y = (yield plusOneLater($x));
            echo "$y\n";
            $z = (yield plusOneLater($y));
            echo "$z\n";
        },
    ];
    echo "All done !!\n";
});
```