javascript で少し時間のかかる API を ajax で並列にたたきたいのですが async/await/promise の意味がわからないので教えてください
const response1 = await $.ajax({type: 'GET', url: url1, data: query1});
const response2 = await $.ajax({type: 'GET', url: url2, data: query2});
console.log(response1 + response2);
ブログ記事をいくつかよんでみた感じだとこういう書き方でリクエストは並列実行されるんでしょうか?
response1 の await があるのでそこで処理がとまって url2 に ajax なげるのは response1 の結果の後になりそうな気がするんですけど…
あと外側の関数に async が必要なのもよくわからなくて何のために必要なんでしょう
java とか C とかのマルチスレッドのイメージとしては
const promise1 = async $.ajax({type: 'GET', url: url1, data: query1});
const promise2 = async $.ajax({type: 'GET', url: url2, data: query2});
const response1 = await promise1;
const response2 = await promise2;
みたいに fork => join が必要な気がするんですが
Promise というのはいわゆる thread オブジェクトとは違うのでしょうか
気になる質問をクリップする
クリップした質問は、後からいつでもMYページで確認できます。
またクリップした質問に回答があった際、通知やメールを受け取ることができます。
バッドをするには、ログインかつ
こちらの条件を満たす必要があります。
回答3件
0
ベストアンサー
そういえばどうなっているんだっけと気になって調べました。
回答というより、自分用のメモです。
何か間違っていたらコメントありがたいです。
概要
const response1 = await $.ajax({type: 'GET', url: url1, data: query1});
const response2 = await $.ajax({type: 'GET', url: url2, data: query2});
console.log(response1 + response2);ブログ記事をいくつかよんでみた感じだとこういう書き方でリクエストは並列実行されるんでしょうか?
並列実行が「マルチプロセス」という意味ならばNOです。
並列実行が「2つのHTTPリクエストが同時に実行されているか」という意味でもNOです。
並列実行が「非同期処理」という意味ならYESです。
response1 の await があるのでそこで処理がとまって url2 に ajax なげるのは response1 の結果の後になりそうな気がするんですけど…
"処理がとまって" awaitしているので止まります(待ちます)
"url2 に ajax なげるのは response1 の結果の後になりそうな気がする" はい、そうです
Promise というのはいわゆる thread オブジェクトとは違うのでしょうか
違います。JavaScriptはマルチスレッドプログラミングはできません。
async/await/promise って?
async,awaitはPromiseの糖衣構文です。
PromiseはJavaScriptで非同期処理を行う際の仕組みです。
詳細
JavaScript(以下js)の非同期処理の仕組みについて記載します。
定義
平行、並列処理
平行処理のことをマルチスレッド、並列処理のことをマルチプロセスとして便宜上扱わせてください
非同期処理
非同期処理は、あるタスクが実行をしている際に、他のタスクが別の処理を実行できる方式である。同期処理では、あるタスクが実行している間、他のタスクの処理は中断される方式である。
I-26-2. 非同期処理と同期処理の実装パターンと特徴 | 日本OSS推進フォーラム
非同期処理 != マルチスレッド(平行処理),非同期処理 != マルチプロセス(並列処理)です
非同期処理を行うための手段としてマルチスレッド、マルチプロセスがあると便宜上扱わせてください
jsが非同期処理に採用している仕組み
jsは(基本的には)シングルプロセス+シングルスレッドで動作しているので、マルチプロセス(並列)もマルチスレッド(平行)も出来ません。
そのため「java とか C とかのマルチスレッド」や他言語のマルチプロセスなどとは違った仕組みで非同期処理を行っています。
これはjsがwebブラウザというGUIアプリケーションのために開発されたプログラミング言語であるためです。
jsが非同期処理に採用している仕組みは「イベントループ」と言います。
JavaScriptは、"event loop"に基づく同時実行モデルを持ちます。このモデルはC言語やJavaのような他の言語のモデルとかなり異なっています。
語弊があるかもしれませんが、処理を開始したら、終わったときに何をするかだけ決めて、それが終わるまで待たずに次の処理を実行していく処理方法です。
jsのイベントループ
ブラウザが画面の描画、イベントの処理(クリックやカーソルでの選択など)、jsの実行などをどのように処理しているかの説明です。
白い四角形はメインスレッドです。これがぐるぐると回って、黄色や青の図形の上に来ると処理を行います。
黄色のTが「タスク」、右側のSLPは画面の描画でSは「styleの計算」、Lは「レイアウトの計算」Pは「ピクセルの描画」です。
Tではイベントの処理とjsの実行が行われます、Promiseのコールバックなども同様です。(基本的には)
この図からjsはシングルスレッドで処理をしていることが分かります。
ここで注意してほしいのはメインスレッドがタスクを通った時でも、場合によっては処理は完了していないかもしれません。
実際にそのタスクが完了したときにPromiseやsetTimeoutのコールバックが新しくタスクに追加されていきます。
jsのイベントループのスケジューリング
実際のスースコードとイベントループの中でタスクがどのように増えて、処理されるのかを見ていきます。
Tasks, microtasks, queues and schedules
1: console.log('script start'); 2: 3: Promise.resolve().then(function() { 4: console.log('promise1'); 5: }).then(function() { 6: console.log('promise2'); 7: }); 8: 9: console.log('script end');
この出力結果は以下のようになります
script start script end promise1 promise2
よくPromiseのインスタンスが作成された時点でコールバックが実行されると言われますが、それは基本的には間違いです(全てではない、ブラウザによっては違う順序になることがあるので)。
上記のリンクでスケジューリングがどのように行われるかをインタラクティブに確認できます。
Promiseとは
jsで処理を非同期に実行するためのものです。
この処理をやって、終わったときにあれをやってねとjsにお願いすることです。
終わったときにあれをやってねは、上図の「Microtasks」に後で登録されていきます。
async/awaitとは
const response1 = await $.ajax({type: 'GET', url: url1, data: query1}); const response2 = await $.ajax({type: 'GET', url: url2, data: query2}); console.log(response1 + response2);
これをPromiseで書き換えると、だいたいこんな感じになります。
$.ajax({type: 'GET', url: url1, data: query1}).then(function() { $.ajax({type: 'GET', url: url2, data: query2}).then(function() { console.info(response1 + response2); }) })
ネストが深くなりますね。
これにリクエストを増やしたりエラー時の処理を追加すると、
あっという間にネストが深くなるためコールバック地獄と恐れられていました。
これを解決するのがもともとC#で発明されたasync/await構文です。
元のコードについて
自分ならこう書くかな。
try { // 両方成功したら返ってくる const responses = await Promise.all([ $.ajax({type: 'GET', url: url1, data: query1}), $.ajax({type: 'GET', url: url2, data: query2}) ]); console.log(responses[0] + responses[1]); // いずれかが失敗するとこっち } catch(e) { // エラー処理 }
投稿2018/08/17 15:38
退会済みユーザー
総合スコア0
0
そもそもpromiseオブジェクトがなんぞやの所をきちんと理解しましょう。
ざっくり解説しますので、続きは勉強してくださいね。
まず、promiseとはnew Promise(fn)
で作られるPromiseクラスのインスタンスです。
promiseインスタンスは「pending」「fulfilled」「rejected」の3つの状態を所持しており、
インスタンスを作った直後に引数のfnが実行されます。
そのfnは引数1がresolve、引数2がrejectという関数固定であり、
これらのどちらかの関数を実行した瞬間ステータスがpendingからどちらかに変化して終わります。
Promiseは.then(fn)
という風に関数を複数設置してチェックポイントを設けながら何度も動かす事が出来ます。
async/awaitはPromiseが頑張って作った設計の割に、
コールバック地獄と比べてコードがあまりイケてなかったので追加実装された糖衣構文です。
async functionはとにかく関数の第一行目をreturn new Promise(function(){ やりたいこと }}
で強引に包んでしまう関数だと思ってください。
質問文ではasync $.ajax
みたいな書き方をしてるけど、そんな書き方ないからね。
async function (引数) { やりたいこと }
という書き方か、
アロー関数を利用してasync () => { やりたいこと }
の二択しかない。
async functionはPromiseでまるっと包んだ糖衣構文と書きましたが、実際には当然resolveとrejectを実行しています。
return
で正常終了すれば、戻り値をresolve
の引数に実行した扱いになります。
throw
で何か投げれば、投げたものがreject
の引数に実行した扱いになります。
awaitはpromiseのpending状態をひたすら待つ糖衣構文です。
実際にやってる事はpromiseに配下の行を全て包んで関数化してしまい、
.then(fn)
として設定してしまう事と同じことが可能です。
awaitの真骨頂はfor文の中身でも平然と使える事で、
普通にPromiseを使ったらfor文なんて使えるはずないですからね。
JavaScriptで実際に動かすならこう
JavaScript
1// とりあえずPromiseオブジェクトとして受け取っておく 2// Promiseはnewした瞬間に実行されるので、ほぼ並列で通信しにいく 3const promise1 = $.ajax({type: 'GET', url: url1, data: query1}); 4const promise2 = $.ajax({type: 'GET', url: url2, data: query2}); 5 6// 結果を順番待ちして受け取る 7const response1 = await promise1; // 流石にpromise2が先に終わってもpromise1が終わらない限りこの行から先には進まないけどね 8const response2 = await promise2;
投稿2018/08/17 11:54
編集2018/08/18 01:55総合スコア21158
バッドをするには、ログインかつ
こちらの条件を満たす必要があります。
退会済みユーザー
2018/08/17 12:24
2018/08/18 02:07
0
元のコードはこんな感じかと思うんですが、
async () => { const response1 = await $.ajax({type: 'GET', url: url1, data: query1}) const response2 = await $.ajax({type: 'GET', url: url2, data: query2}) console.log(response1 + response2) }
上記のコードは、次のコードと等価です。お察しの通りこれはAPIに順次アクセスします。
async () => { const promise1 = $.ajax({type: 'GET', url: url1, data: query1}) const response1 = await promise1 const promise2 = $.ajax({type: 'GET', url: url2, data: query2}) const response2 = await promise2 console.log(response1 + response2) }
行の順番を入れ替えて次のようにすると並列アクセスするようになります。
$.ajax の前に async は無いのが正しいです。
async () => { const promise1 = $.ajax({type: 'GET', url: url1, data: query1}) const promise2 = $.ajax({type: 'GET', url: url2, data: query2}) const response1 = await promise1 const response2 = await promise2 console.log(response1 + response2) }
なお async ですが、関数宣言の前に付けることでその関数内で await 構文を使えるようにするというものです。
async を付けないと await が使えないので、 promise の then を使って次のように書くことになります。
だいぶ複雑になります。
() => { const promise1 = $.ajax({type: 'GET', url: url1, data: query1}) const promise2 = $.ajax({type: 'GET', url: url2, data: query2}) return promise1.then(response1 => { return promise2.then(response2 => { console.log(response1 + response2) }) }) }
投稿2018/08/17 12:23
総合スコア2413
バッドをするには、ログインかつ
こちらの条件を満たす必要があります。
あなたの回答
tips
太字
斜体
打ち消し線
見出し
引用テキストの挿入
コードの挿入
リンクの挿入
リストの挿入
番号リストの挿入
表の挿入
水平線の挿入
プレビュー
質問の解決につながる回答をしましょう。 サンプルコードなど、より具体的な説明があると質問者の理解の助けになります。 また、読む側のことを考えた、分かりやすい文章を心がけましょう。
バッドをするには、ログインかつ
こちらの条件を満たす必要があります。