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

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

ただいまの
回答率

90.50%

  • JavaScript

    16467questions

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

async function/await/promiseの動きがわからない

解決済

回答 3

投稿 編集

  • 評価
  • クリップ 2
  • VIEW 610

sweet-potato

score 1

 発生している問題

お世話になります。
async function/await/promiseの実行される順番がよくわからないので教えてください。

  1. なぜ1 5と行かないでasync functionの中に入っていくのですか?(No.1/No.2)
  2. なぜawaitがあるとasync functionの外に行くのですか?(No.1)
  3. なぜ2 4と行かないで2 3 4と表示されるのですか?(No.2)

 該当のソースコード

function pro(){
  return new Promise((res)=>{
    console.log(3);
    res();
  });
}

// No.1
console.log(1);
(async function(){
  console.log(2);
  await pro();
  console.log(4);
})();
console.log(5);
// 1 2 3 5 4

// No.2
console.log(1);
(async function(){
  console.log(2);
  pro();
  console.log(4);
})();
console.log(5);
// 1 2 3 4 5
  • 気になる質問をクリップする

    クリップした質問は、後からいつでもマイページで確認できます。

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

    クリップを取り消します

  • 良い質問の評価を上げる

    以下のような質問は評価を上げましょう

    • 質問内容が明確
    • 自分も答えを知りたい
    • 質問者以外のユーザにも役立つ

    評価が高い質問は、TOPページの「注目」タブのフィードに表示されやすくなります。

    質問の評価を上げたことを取り消します

  • 評価を下げられる数の上限に達しました

    評価を下げることができません

    • 1日5回まで評価を下げられます
    • 1日に1ユーザに対して2回まで評価を下げられます

    質問の評価を下げる

    teratailでは下記のような質問を「具体的に困っていることがない質問」、「サイトポリシーに違反する質問」と定義し、推奨していません。

    • プログラミングに関係のない質問
    • やってほしいことだけを記載した丸投げの質問
    • 問題・課題が含まれていない質問
    • 意図的に内容が抹消された質問
    • 広告と受け取られるような投稿

    評価が下がると、TOPページの「アクティブ」「注目」タブのフィードに表示されにくくなります。

    質問の評価を下げたことを取り消します

    この機能は開放されていません

    評価を下げる条件を満たしてません

    評価を下げる理由を選択してください

    詳細な説明はこちら

    上記に当てはまらず、質問内容が明確になっていない質問には「情報の追加・修正依頼」機能からコメントをしてください。

    質問の評価を下げる機能の利用条件

    この機能を利用するためには、以下の事項を行う必要があります。

質問への追記・修正、ベストアンサー選択の依頼

  • mts10806

    2018/07/19 13:00

    質問編集画面タイトル横にある「初心者アイコン」をご活用ください。「初心者」と質問で書くよりも伝わりますし、質問一覧に表示されるのでわかりやすくなります。

    キャンセル

  • mts10806

    2018/07/19 13:01

    試した上で分かったことを記載してください。それぞれ調べればある程度目処はつくはずです。https://teratail.com/help/question-tips#questionTips2-1

    キャンセル

  • sweet-potato

    2018/07/19 14:41

    アドバイスありがとうございます!承知致しました。

    キャンセル

回答 3

checkベストアンサー

+8

async付きの関数は、本質的にはPromiseを返す関数です。awaitなしで呼べばPromiseを受け取ることもできますし、逆にasync表記でない、返り値がPromiseの関数をawaitさせることもできます。

そして、async-awaitの仕様を読み解いてみると、awaitが現れるまでの間は同期的に実行されることとなります。簡単に言えば、await以降の行を、awaitで待つPromise.then()に切り出したような感じになります。

つまり、

なぜ1 5と行かないでasync functionの中に入っていくのですか?(No.1/No.2)

async関数といってもawaitまでは同期的に実行されるからです。

なぜawaitがあるとasync functionの外に行くのですか?(No.1)

async 式と書いた場合も、「式」を評価してからそこにthenをぶら下げる、というような感じで処理が進みます。

なぜ2 4と行かないで2 3 4と表示されるのですか?(No.2)

どこでもawaitしていないのですべてが同期的に進んでいます(proで生成したPromiseもただ捨てられています)。

投稿

編集

  • 回答の評価を上げる

    以下のような回答は評価を上げましょう

    • 正しい回答
    • わかりやすい回答
    • ためになる回答

    評価が高い回答ほどページの上位に表示されます。

  • 回答の評価を下げる

    下記のような回答は推奨されていません。

    • 間違っている回答
    • 質問の回答になっていない投稿
    • スパムや攻撃的な表現を用いた投稿

    評価を下げる際はその理由を明確に伝え、適切な回答に修正してもらいましょう。

  • 2018/07/20 08:06

    回答ありがとうございます。
    大変参考になりました!

    キャンセル

+5

 修正というか正しい非同期処理とその同期的処理

お二方から指摘がありますが、 pro() は呼び出した瞬間同期的に console.log(3) が実行されます。
というのも Promise を生成してますが引数に与えられたコールバック関数の中でいきなり console.log(3) としているからですね。
例えば 3 秒待った後に 3 と出力 するような非同期関をコールバック関数として引数に与えるのならば以下のようになります。

const output3 = () => new Promise(resolve => {
  setTimeout(() => {
    console.log('3');
    resolve();
  }, 3000)
});

そして以下のコードを試してみてください。

output3();
console.log(2);
console.log(1);

3 2 1 と出力されそうですが、実際は 2 1 3 となります。

というのも output3 が Promise を返す非同期処理だからですね。
output3 の Promise の処理が完了していることを期待して、 次の console.log() を実行するには await キーワードを使用して、 Promise の完了を待つ必要があります。
しかし、 await キーワードは async function の中でしか使えないという制約があるので以下のようにします。
(トップレベル await が可能な仕組みが現在作られています。ちなみに GoogleChrome の デベロッパーコンソールもトップレベル await が可能)

async function() {
  await output3();
  console.log(2);
  console.log(1);
}

関数宣言しただけでは動かないので即時呼び出しする(即時関数)

(async function() {
  await output3();
  console.log(2);
  console.log(1);
})();

これを実行すると、めでたく 3秒待って 3が出力され、続いて 2, 1 となります。💮


なぜ1 5と行かないでasync functionの中に入っていくのですか?(No.1/No.2)

即時関数だから。さらに async function なので 2 の次に 5 が来るか、 3 が来るかは保証できません。 

なぜawaitがあるとasync functionの外に行くのですか?(No.1)

外へ行ってません。 関数 pro を呼び出しているだけ。

なぜ2 4と行かないで2 3 4と表示されるのですか?(No.2)

これは例が悪いですね。 pro() がもっと時間のかかる処理なら 1 2 4 5 3 となりそうですが、すぐ終わる処理なので順当に 1 2 3 4 5 となりました。これも async function なので 2 の後が 5 なのか 3 なのかは保証できません。


別に珍しい質問ではないし既に先人が抱いてきた疑問なので 非同期処理 で調べればいくらでも詳しい説明は出てきそうですけどね。それをこの場で説明してくださいというのは、何も調べていないということではないでしょうか。

投稿

編集

  • 回答の評価を上げる

    以下のような回答は評価を上げましょう

    • 正しい回答
    • わかりやすい回答
    • ためになる回答

    評価が高い回答ほどページの上位に表示されます。

  • 回答の評価を下げる

    下記のような回答は推奨されていません。

    • 間違っている回答
    • 質問の回答になっていない投稿
    • スパムや攻撃的な表現を用いた投稿

    評価を下げる際はその理由を明確に伝え、適切な回答に修正してもらいましょう。

  • 2018/07/19 13:32 編集

    自分が別に回答しましたが、この場合1つ目でも「1 の次に 5 が来る」ようなことは起きません。「asyncなのに同期的に動く」ということのほうがこの疑問に対する回答のような気がします。

    キャンセル

  • 2018/07/19 13:43

    ああ、そうでした。正しくは 2 の後に、 5 か 3 ですね訂正します。

    > 「asyncなのに同期的に動く」ということのほうがこの疑問に対する回答
    これちょっとわからないです。どういうことでしょうか。

    キャンセル

  • 2018/07/19 13:46

    自分の回答をお読みください。async functionでも、await以前(awaitがなければ全体)は同期的に実行されます。

    キャンセル

  • 2018/07/19 14:24 編集

    んーすみません。今の回答で間違っている箇所があれば引用で直接指摘していただけると自分の誤解もあるかと思うので認識のズレをなくせると思うのですが、もしかして、"1 の次に 5 が来る" ことについておっしゃらてます?もしそうであれば訂正したのでご勘弁ください。

    それとも async function の中身と外の console.log() が同期的に処理されるという主張ですか?

    キャンセル

  • 2018/07/19 14:39

    >「async function なので 2 の次に 5 が来るか、 3 が来るかは保証できません。 」

    new Promiseに渡したコールバック自体も同期実行なので、2の次は3で確定です。

    >「これも async function なので 2 の後が 5 なのか 3 なのかは保証できません。」

    async functionですが、awaitがないので同期的に実行され、さらにnew Promiseに渡したコールバック自体も同期実行なので、1→2→3→4→5に決まります。

    キャンセル

  • 2018/07/19 14:54

    あーー!なるほど。自分で勝手な前提を作ってました。コレばっかりは申し訳ないです。
    質問者の提示のコード中の pro 関数が Promise 返してるけどなんの非同期処理もせずいきなり console.log() 吐いてるんですよね。これを勝手に読み替えて何かしらの重い処理をした後に resolve で 3 を返すと解釈してました。
    つまりはログを追っかけるだけなら pro 関数 は `() => console.log(3)` と等価なんですよね。

    maisumakun さんの回答の通り async function は Promise を返すから中と外は非同期だから注意してねって主張をしたかったんですが、質問者提示中の pro() を尊重するのであれば 私の回答は完全に間違いですね。

    キャンセル

  • 2018/07/19 15:15

    回答ありがとうございます!
    みなさんが通ってきた道だと聞いてなんだか安心しました。
    自分の努力が足りず申し訳ありませんでした。
    なんとか乗り越えます!

    キャンセル

+3

現象としてはmaisumakunさんや、og24715さんが挙げたのが理由かと思いますが、
基本的にPromiseを返すようなライブラリやモジュールってのは、
速攻で別のページにAjaxでアクセスしにいったり、FileAPIで読み書きしたり…といった風に非同期的に行われるものだからです。

なので直感的に思い描いてた動作とは違う事になっていますが、
理屈は単純で、実際に非同期の処理で切り離されるまで、全ての同期処理を面倒見るからです。
サンプルコードはPromiseを使いこそはしていますが、中身は全て同期的な処理なので全て面倒をみている事がわかります。

await pro()の下のconsole.log(4)が遅延しているのは、
awaitという糖衣構文によりpro().then(() => { console.log(4) })を見てくれだけ修正しているからです。

もしpro関数内部でsetTimeout等を使って0ms後に発火する非同期処理を経由するよう修正すれば、
思い描いていた挙動に近づけるかと思います。

投稿

  • 回答の評価を上げる

    以下のような回答は評価を上げましょう

    • 正しい回答
    • わかりやすい回答
    • ためになる回答

    評価が高い回答ほどページの上位に表示されます。

  • 回答の評価を下げる

    下記のような回答は推奨されていません。

    • 間違っている回答
    • 質問の回答になっていない投稿
    • スパムや攻撃的な表現を用いた投稿

    評価を下げる際はその理由を明確に伝え、適切な回答に修正してもらいましょう。

  • 2018/07/19 14:53

    「awaitなしのasync function」なんてのも成立するんですね…何に使えばいいかよくわからないですが。

    キャンセル

  • 2018/07/19 15:31

    async関数は単に中身を`return new Promise(fn)`でまるっと囲んだ関数の糖衣構文なので、
    「awaitなしのasync function」は成立しますよ。
    単純に他の人にawait付きで実行してもらう為の関数宣言ですね。

    キャンセル

  • 2018/07/20 08:11 編集

    回答ありがとうございます。
    これを参考にもっと探求します!

    キャンセル

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

  • ただいまの回答率 90.50%
  • 質問をまとめることで、思考を整理して素早く解決
  • テンプレート機能で、簡単に質問をまとめられる

関連した質問

同じタグがついた質問を見る

  • JavaScript

    16467questions

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