75
66

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

More than 5 years have passed since last update.

ReactPHP?時代はRecoilPHPだ!

Last updated at Posted at 2016-01-23

時代変遷

JavaScript では

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

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

  • 1→2→3
  • 4→5→6

callback (旧石器時代)

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)

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)

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**というものが登場し,言語ネイティブでのイベントループは実現できませんが,工夫をこらせばJavaScriptっぽい書き方も出来るようになってきました.

ReactPHP (PHP5, 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 というものも出てきたようで,ジェネレータを使った書き方も出来るようになってました!以下の点で優位性があると思います.

  • functionの数が少なくて済む
  • ReactPHPではグローバル変数としていたイベントループが,RecoilPHPではクラス内部にstatic変数としてあらかじめ用意されている (推測)
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だと少し残念なことになります(笑)

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地獄を回避する裏ワザ」

2016/08/04 追記: mpyw/co を使った場合

このライブラリは

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

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

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";
});
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";
});
75
66
2

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
75
66

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?