質問をすることでしか得られない、回答やアドバイスがある。

15分調べてもわからないことは、質問しよう!

新規登録して質問してみよう
ただいま回答率
85.50%
JavaScript

JavaScriptは、プログラミング言語のひとつです。ネットスケープコミュニケーションズで開発されました。 開発当初はLiveScriptと呼ばれていましたが、業務提携していたサン・マイクロシステムズが開発したJavaが脚光を浴びていたことから、JavaScriptと改名されました。 動きのあるWebページを作ることを目的に開発されたもので、主要なWebブラウザのほとんどに搭載されています。

Q&A

解決済

3回答

6689閲覧

javascript の並列実行 async/await/promise って?

退会済みユーザー

退会済みユーザー

総合スコア0

JavaScript

JavaScriptは、プログラミング言語のひとつです。ネットスケープコミュニケーションズで開発されました。 開発当初はLiveScriptと呼ばれていましたが、業務提携していたサン・マイクロシステムズが開発したJavaが脚光を浴びていたことから、JavaScriptと改名されました。 動きのあるWebページを作ることを目的に開発されたもので、主要なWebブラウザのほとんどに搭載されています。

0グッド

4クリップ

投稿2018/08/17 10:41

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ページで確認できます。

またクリップした質問に回答があった際、通知やメールを受け取ることができます。

バッドをするには、ログインかつ

こちらの条件を満たす必要があります。

guest

回答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アプリケーションのために開発されたプログラミング言語であるためです。

GUIは何故シングルスレッドなのか

jsが非同期処理に採用している仕組みは「イベントループ」と言います。

JavaScriptは、"event loop"に基づく同時実行モデルを持ちます。このモデルはC言語やJavaのような他の言語のモデルとかなり異なっています。

並列モデルとイベントループ

語弊があるかもしれませんが、処理を開始したら、終わったときに何をするかだけ決めて、それが終わるまで待たずに次の処理を実行していく処理方法です。

jsのイベントループ

ブラウザが画面の描画、イベントの処理(クリックやカーソルでの選択など)、jsの実行などをどのように処理しているかの説明です。

THE EVENT LOOP

イメージ説明

白い四角形はメインスレッドです。これがぐるぐると回って、黄色や青の図形の上に来ると処理を行います。

黄色の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 - MDN

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
miyabi-sun

総合スコア21158

バッドをするには、ログインかつ

こちらの条件を満たす必要があります。

oikashinoa

2018/08/17 12:23

> そのfnは引数1がresolve、引数2がresolveという関数固定であり、 MDN見てると引数2はrejectかと思ったんですが違うのでしょうか?勉強中に付きボケてたらスミマセン。
退会済みユーザー

退会済みユーザー

2018/08/17 12:24

>async functionはとにかく関数の第一行目をreturn new Promise(function(){ やりたいこと }}で強引に包んでしまう関数 じゃあ async は中のメソッドすべてを fork で実行して 関数の中でメソッド返り値と依存関係のある変数を使うときだけ await をかけってこと? async f = () => { sessionStorage.setItem('x', 1); sessionStorage.setItem('x', 2); console.log(sessionStorage,getItem('x')); } こう書いた場合 console.log の結果は 1 も 2 も undefined もありえるってことなのかな const g = () => { f(); f(); } async の外側絡みた場合どうなるんでしょう ってよんだら 1 回目の f と 2 回目の f の順番は保証される? 2 回目の f の console.log は 1 か 2 で undefined じゃないことは保証されるのかな
miyabi-sun

2018/08/18 02:07

> oikashinoaさん 失礼しました、typo修正しました。 > katingさん > 関数の中でメソッド返り値と依存関係のある変数を使うときだけ await をかけってこと? そうです。 pending状態のpromiseのインスタンスの前にawait付けましょう。 そうすればresolve(value)が実行されるまでずっと待つ > `async f = () => {` いやだからそんな構文なくて、`var f = async () => {`が正解です。 そして、手書きしたその3行は全て同期処理なので同期的に実行されて終了します。 従ってconsole.logの結果は2しかありえません。 > `const g = () => {` 開始タイミングの順番は保証されます。 JavaScriptの同期的に書いた箇所は保証されますので、単に`new Promise(fn)`で包んでみただけだと同期的に実行されて終わります。 実際にはPromiseというのは大抵がHTTP通信やファイルアクセスなんかの外部APIに投げて非同期にするから用いられる手法ですので、外部モジュールやAPIがPromiseを返す場合は初期処理までは保証されますが、それ以降は保証されません。 従って、後者のgを実行した場合、2が2回表示されるだけです。
guest

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

set0gut1

総合スコア2413

バッドをするには、ログインかつ

こちらの条件を満たす必要があります。

あなたの回答

tips

太字

斜体

打ち消し線

見出し

引用テキストの挿入

コードの挿入

リンクの挿入

リストの挿入

番号リストの挿入

表の挿入

水平線の挿入

プレビュー

15分調べてもわからないことは
teratailで質問しよう!

ただいまの回答率
85.50%

質問をまとめることで
思考を整理して素早く解決

テンプレート機能で
簡単に質問をまとめる

質問する

関連した質問