深夜にTwitterでGuzzle(PHPのHTTPクライアントライブラリ)がPromises/A+(日本語訳)のインターフェイスを持ってるって話が出てたので、深夜のテンションでサンプルコードを書いたらこうなった。
あと、わーい、すごーいの便乗。
<?php
include_once getenv('HOME') . '/.composer/vendor/autoload.php';
use GuzzleHttp\Client;
$client = new Client(['allow_redirects' => ['on_redirect' => function () {
throw new \Exception();
}]]);
$kaban = say("かばん");
$sarval = say("サーバル");
$sarval("とまれーっ");
//$bus = $client->requestAsync('POST', 'http://kemono-friends.jp/library'
$bus = $client->requestAsync('POST', 'http://localhost/library'
)->then(function () use ($sarval) { $sarval("たーのしー"); },
function () use ($sarval) { $sarval("ヴッ "); sleep(1); }
)->then(function () use ($kaban) { $kaban("あぶないよー"); });
sleep(3);
$sarval("うごけーっ");
$bus->wait();
exit;
function say($name) {
return function ($script) use ($name) {
printf("%s「%s」\t[%s]\n", $name, $script, date('H:i:s'));
};
}
PHPで非同期処理ができるのか、といったら実はちょっと厳しい。上記の処理では$bus->wait()
が呼び出されるまで、待てど暮せど何も起こらない。
もちろんGuzzleのrequestAsync()
はお遊びで実装されてるわけではなくて、実用的な利用方法は @suzuki さんのGuzzle Promiseを使った
非同期処理によるAPIコールの高速化 // Speaker Deckに詳しい。
しかし「非同期処理」と呼ぶと、おそらく間違ってはないものの若干の期待を抱かせすぎなところはあって、「複数のHTTPリクエストを効率的に処理することができる」のような理解をしておくのが妥当だろう。
以下のようなコードがどのように実行されるかを見ると、どのような挙動をするのか理解できるかもしれない。
<?php
include_once getenv('HOME') . '/.composer/vendor/autoload.php';
use GuzzleHttp\Client;
use GuzzleHttp\Pool;
use GuzzleHttp\Psr7\Request;
$kaban = say("かばん");
$sarval = say("サーバル");
$sarval("とまれーっ");
$resuests = function () use ($sarval) {
while (true) {
$sarval("うごけーっ");
yield new Request('GET', 'http://localhost/library');
}
};
$client = new Client(['allow_redirects' => ['on_redirect' => function () {
throw new \Exception();
}]]);
$pool = new Pool($client, take($resuests(), 7), [
'concurrency' => 3,
'rejected' => function () use ($sarval, $kaban) {
$sarval("ヴッ ");
sleep(1);
$kaban("あぶないよー");
}
]);
$promise = $pool->promise();
$promise->wait();
$kaban("あははははは");
exit;
function say($name) {
return function ($script) use ($name) {
printf("%s「%s」\t[%s]\n", $name, $script, date('H:i:s'));
};
}
function take($iter, $n) {
$i = 0;
foreach ($iter as $k => $x) {
if ($n <= $i++) { break; }
yield $k => $x;
}
}
サーバルちゃんは3並列で合計7回バスにぶつけられる運命にある。 (自分で書いておいて意味がわからない)
サーバル「とまれーっ」 [09:40:11]
サーバル「うごけーっ」 [09:40:11]
サーバル「うごけーっ」 [09:40:11]
サーバル「うごけーっ」 [09:40:11]
サーバル「ヴッ 」 [09:40:11]
かばん「あぶないよー」 [09:40:12]
サーバル「うごけーっ」 [09:40:12]
サーバル「ヴッ 」 [09:40:12]
かばん「あぶないよー」 [09:40:13]
サーバル「うごけーっ」 [09:40:13]
サーバル「ヴッ 」 [09:40:13]
かばん「あぶないよー」 [09:40:14]
サーバル「うごけーっ」 [09:40:14]
サーバル「ヴッ 」 [09:40:14]
かばん「あぶないよー」 [09:40:15]
サーバル「うごけーっ」 [09:40:15]
サーバル「ヴッ 」 [09:40:15]
かばん「あぶないよー」 [09:40:16]
サーバル「うごけーっ」 [09:40:16]
サーバル「ヴッ 」 [09:40:16]
かばん「あぶないよー」 [09:40:17]
サーバル「ヴッ 」 [09:40:17]
かばん「あぶないよー」 [09:40:18]
かばん「あははははは」 [09:40:18]
最初のサーバル「うごけーっ」 [09:40:11]
が三回呼ばれてるのが比較的良い感じっぽく見えるが、そのあとサーバル「ヴッ 」
と かばん「あぶないよー」
は必ず並び、しかもsleep(1)
で処理全体をブロックしてしまってることがわかる。
このことから、GuzzleのrequestAsync()
は飽くまで非同期リクエストのための機能で、それ以外の処理は非同期にならないことを注意しておくと良い。
ではPHPでは非同期処理できないのかと言ったらそうでもなく、 @niisan-tokyo さんのPHPで作る非同期処理 // Speaker Deckに詳しいように様々な試みがあって、ReactPHP?時代はRecoilPHPだ!など参照。
環境について
composer global show
すると、こんな感じ。composer global guzzlehttp/guzzle
すると入るかも。
guzzlehttp/guzzle 6.2.2 Guzzle is a PHP HTTP client library
guzzlehttp/promises v1.3.1 Guzzle promises library
guzzlehttp/psr7 1.3.1 PSR-7 message implementation
さいごに
深夜のテンションで書いたサンプルコードを適当に深掘りしていった結果、意味不明な記事になった。
たーのしー