時代変遷
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";
});