質問: **最終的に知りたいこと** ・複数APIへのリクエストを行っている時、すべてのリクエスト処理結果を待たず(先に)ページ表示することが出来るでしょうか? PHPで **質問背景** ・PHPは同期処理しかできないと思っていたのですが、誤った認識でしょうか? ・非同期の意味がAjaxとは異なる? ・非同期ではなく、並列処理??
回答: > > ・PHPは同期処理しかできないと思っていたのですが、誤った認識でしょうか? > > > 基本的に同期処理しかできませんが, * ストリームリソース (`stream_socket_client`などで作れる) * cURLリソース (`curl_init`で作れる) * `mysqli`クラス など,幾つかの特別な実装は **「一定時間を上限として待機し,結果を返すことができれば返し,できなければ何もしない」** ということができる関数やメソッドを持っています。 * `stream_select` (但し`stream_set_blocking`でブロッキングを解除しておく必要がある) * `curl_multi_select` * `mysqli::poll` curl_multiでHTTP並行リクエストを行うサンプル - Qiita 上記はcURLの例ですが, ``` do switch (curl_multi_select($mh, $TIMEOUT)) { /* ... */ } while ($running); ``` このループの部分,ここを**イベントループ**と呼びます。「何か動きがあった」というのがイベントです。イベントがあるたびにループの中身を実行するので,イベントループです。ただし,こういう処理はそのままでは非常に書きにくいので,通常はJavaScriptが言語レベルでそう実装されているように... 1. 素のイベントループで書く 2. イベントループ中に書かれた具体的な処理をコールバック関数に委譲する 3. コールバック関数をPromiseオブジェクトでラップし,thenのチェインで書けるようにする 4. Promiseでthenを繋ぐ代わりに,Generator(Coroutine)に対してyieldすることで,ほとんど見た目が同期処理のように書けるようにする 上記のように,使いやすいように抽象化していきます。この話題に関しては以下の記事が詳しいです。 JavaScriptは如何にしてAsync/Awaitを獲得したのか Qiita版 - Qiita > > ・複数APIへのリクエストを行っている時、すべてのリクエスト処理結果を待たず(先に)ページ表示することが出来るでしょうか? PHPで > > > できます。以下に一番最初に正常に返ってきたレスポンスを表示し,残りはバックグラウンドで処理する例を示します。これは上記で示した4番までの抽象化を行っています。 HTTP通信を非同期で捌くライブラリとしてはGuzzleが有名ですが,少々機能過剰なのと,単に私が使い慣れていないこともあるので,自作したcoのほうを使わせていただきます。Guzzleのほうが綿密にオブジェクトが作り込まれている一方,coは直接cURLリソースを触るようにしてシンプルさ優先にしています。 ``` $url, CURLOPT_RETURNTRANSFER => true, CURLOPT_FAILONERROR => true, ]); $content = yield $ch; // リクエストを非同期で飛ばす $length = strlen($content); if (!headers_sent()) { // まだヘッダを送出していなければ,計算したContent-Lengthなどをヘッダに予約する header('Content-Type: text/plain; charset=UTF-8'); header("Content-Length: $length"); // データを初めてechoするタイミングで同時にヘッダも送出される echo $content; } }, $urls); // タスクを実行 Co::wait($tasks, ['throw' => false]); ``` > > ・非同期の意味がAjaxとは異なる? > > > Ajaxは *Asynchronous JavaScript + XML* の略であり, 本来は `XMLHttpRequest` オブジェクトが絡む非同期処理を指すと思いますが,最近では **「JavaScriptから非同期にHTTPリクエストを飛ばす処理」** という意味で使われていると思います。(実際,`window.fetch`など別の方法もあります) 【Ajax】 WebブラウザはHTML+JavaScriptをあらかじめ受信しておく → JavaScriptでアプリケーションサーバに対して非同期なHTTPリクエストを飛ばす → 複数のPHPプロセスがアプリケーションサーバで起動 → それぞれが独立してAPIサーバからのレスポンスを返す 【今回の方法】 WebブラウザがHTMLを受信するためのHTTPリクエストをアプリケーションサーバに飛ばす → 単一のPHPプロセスがアプリケーションサーバで起動 → イベントループを実行,その結果一番早く得られたAPIサーバからのレスポンスを返す > > ・非同期ではなく、並列処理?? > > > 非同期であるから,並行処理ができるんです。 同期的だったら1個ずつブロッキングがかかるので,逐次処理しかできません。 また,並列処理と並行処理は微妙に違います。 parallel と concurrent、並列と並行の違い - 本当は怖い情報科学 今回紹介したようなHTTPリクエストの平行化は,**レスポンスが返ってくるまでの無駄な待ち時間を他のHTTPリクエストに充てる**というアイデアであり,CPU上で同時に複数のHTTPリクエストを飛ばすための処理が実行されているわけではありません。