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

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

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

Node.jsとはGoogleのV8 JavaScriptエンジンを使用しているサーバーサイドのイベント駆動型プログラムです。

JavaScript

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

Q&A

解決済

1回答

1446閲覧

非同期処理の挙動が分からない async

Patao150205

総合スコア10

Node.js

Node.jsとはGoogleのV8 JavaScriptエンジンを使用しているサーバーサイドのイベント駆動型プログラムです。

JavaScript

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

0グッド

1クリップ

投稿2021/04/14 08:35

前提・実現したいこと

質問です。
asyncを関数につけると、その関数は非同期関数として扱われると思うのですが、なぜ、実行結果が、
こんにちは! => 非同期じゃないの? => 違うんだねの順番にならないのですか?

_emitFizzBuzzは、非同期関数ですよね?

該当のソースコード

const events = require("events"); function createFizzBazzEventEmitter(untill) { const eventEmitter = new events.EventEmitter(); _emitFizzBuzz(eventEmitter, untill); console.log("こんにちは!"); return eventEmitter; } async function _emitFizzBuzz(eventEmitter, untill) { console.log("非同期じゃないの?"); console.log("違うんだね"); eventEmitter.emit("start"); let count = 1; while (count <= untill) { await new Promise((resolve) => setTimeout(resolve, 100)); if (count % 15 === 0) { eventEmitter.emit("FizzBuzz", count); } else if (count % 3 === 0) { eventEmitter.emit("Fizz", count); } else if (count % 5 === 0) { eventEmitter.emit("Buzz", count); } count++; } eventEmitter.emit("end"); } function startListener() { console.log("start"); } function fizzListener(count) { console.log("Fizz", count); } function buzzListener(count) { console.log("Buzz", count); } function fizzBuzzListener(count) { console.log("FizzBuzz", count); } function endListener() { console.log("end"); this.off("start", startListener) .off("Fizz", fizzListener) .off("Buzz", buzzListener) .off("FizzBuzz", fizzBuzzListener) .off("end", endListener); } createFizzBazzEventEmitter(40) .on("start", startListener) .on("Fizz", fizzListener) .once("Buzz", buzzListener) .on("FizzBuzz", fizzBuzzListener) .on("end", endListener);

###実行結果

非同期じゃないの?
違うんだね
こんにちは!
Fizz 3
Buzz 5
Fizz 6
Fizz 9
Fizz 12
FizzBuzz 15
Fizz 18
Fizz 21
Fizz 24
Fizz 27
FizzBuzz 30
Fizz 33
Fizz 36
Fizz 39
end

気になる質問をクリップする

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

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

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

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

guest

回答1

0

ベストアンサー

asyncを関数につけると、その関数は非同期関数として扱われると思うのですが

非同期関数は実行した瞬間から非同期になる訳ではありません。
結果を受け取る手続きが非同期になるだけです。


まず非同期処理の目的です。
これは何時終わるか分からん低速な処理、
例えばHDD内部のファイルへのタッチ、HTTP通信の結果を待つ時に後回しにする為に利用されるものです。

ということはですよ、
そもそもの読み込みスタート!とかHTTPリクエストを送信する箇所は早く実行して欲しいわけです。

早くHDDからファイルが読み出したい、HTTPレスポンスを受け取りたいから上にコード書いてるのに、
肝心の作業開始が遅延されたら、一体何やってるのかわかりません。

なのでnew Promise(fn)でPromiseインスタンスを生成した時、
fnの関数は同期処理で一気に実行されます。


async関数はPromiseを上手く扱う為の糖衣構文でしかありません。
具体的には関数の内部をnew Promise(resolve => fn)で包んだような挙動をします。

Promiseはresolveのタイミングで
一度イベントループを介してやり取りするので非同期になります。

イベントループの仕様としてresolve扱いで正常終了になったPromiseに、
.then(fn)が引っ付いていれば中身の関数を実行するというフェイズが存在します。
異常終了なら.catch(fn)を実行するみたいなルールもあります。


以上を踏まえて

こういう風にawait構文を開幕で挟んで、
一度resolve->.then(fn)を挟む事を意識したコードを記述すれば
開幕から非同期になるはずです。

js

1const waitFor = async ms => 2 new Promise(resolve => setTimeout(resolve, ms)); 3 4async function _emitFizzBuzz(eventEmitter, untill) { 5 await waitFor(0); 6 console.log("非同期じゃないの?"); 7 console.log("違うんだね"); 8 eventEmitter.emit("start"); 9 let count = 1; 10 while (count <= untill) { 11 await waitFor(100); 12 if (count % 15 === 0) { 13 eventEmitter.emit("FizzBuzz", count); 14 } else if (count % 3 === 0) { 15 eventEmitter.emit("Fizz", count); 16 } else if (count % 5 === 0) { 17 eventEmitter.emit("Buzz", count); 18 } 19 count++; 20 } 21 eventEmitter.emit("end"); 22}

async functionを開きます。
これは「糖衣構文」なので現実世界に存在するコードではありません。
実行される直前に正規のJSコードに変換されて実行される疑似コードです。

上記の_emitFizzBuzzは実際にはこんなコードになっています

js

1const waitFor = async ms => 2 new Promise(resolve => setTimeout(resolve, ms)); 3 4function _emitFizzBuzz(eventEmitter, untill) { 5 return new Promise(resolve => { 6 waitFor(0).then(() => { 7 console.log("非同期じゃないの?"); 8 console.log("違うんだね"); 9 eventEmitter.emit("start"); 10 let count = 1; 11 12 // ループ文の収まりをPromiseだけで記述するのは超大変なので、これで合ってるかは自信ない 13 let promise = Promise.resolve(null); 14 while (count <= untill) { 15 promise = promise 16 .then(() => waitFor(100)) 17 .then(() => count) 18 .then(count => { 19 if (count % 15 === 0) { 20 eventEmitter.emit("FizzBuzz", count); 21 } else if (count % 3 === 0) { 22 eventEmitter.emit("Fizz", count); 23 } else if (count % 5 === 0) { 24 eventEmitter.emit("Buzz", count); 25 } 26 }); 27 count++; 28 } 29 promise = promise.then(() => { 30 eventEmitter.emit("end"); 31 resolve(); 32 }); 33 }) 34 }) 35}

Promiseだけでループ文を表現するとしたらこういったコードになるはずです。
なんでJSでループ文使いたいだけで、
こんなバグがあちこちに入りそうなコード書かされるんだって話になるじゃないですか。

そのためにasync/awaitはES2017という新しいバージョンで追加されました。
これでPromiseなんたらを書きまくる必要はなくなりましたが、
await演算子を使ったら、右辺でPromise.then(fn)メソッドを実行して、値を取り出すまで待とうとしているんだなというニュアンスの理解は必要となります。

なのでasync/awaitを使っていたとしても、
どうやってPromiseを操っているのかを抑えるようにしてください。

投稿2021/04/14 10:12

編集2021/04/20 14:24
miyabi-sun

総合スコア21158

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

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

Patao150205

2021/04/14 14:53

どうも、こんにちは! > 肝心の作業開始が遅延されたら、一体何やってるのかわかりません。 こんにちは!以降の処理を素早く行うため、非同期そのものの処理を早く終わらせるために先頭にawaitを持ってきて早めに非同期を実行している認識でいいでしょうか? あと、await以降は,こんにちは!以降の処理から切り離された、非同期処理になるということですよね? 非同期処理への理解が、あやふやなままになってました。 >一度resolve->.then(fn)を挟む事を意識したコードを記述すれば ここがよくわからないです。
Patao150205

2021/04/16 08:40

なるほどお!Promiseに置きかえて見ると理解できました! もう一つ質問があって,質問の最初の実行結果だと、asyncの冒頭が同期処理になっててstartのイベントが登録される前にイベントが発行されてconsole.logで表示されないみたいですが、書き換え後は、冒頭から非同期で、非同期処理そのものが後回しにされるので、startが表示されるようになるということですよね? 同期処理Fastで、非同期処理 = 後回し のイメージでいいですかね?
miyabi-sun

2021/04/16 08:57

もしこの辺の仕様を詳細まで把握したいなら Node.jsの「イベントループ」の仕様を勉強してみてください。 https://blog.hiroppy.me/entry/nodejs-event-loop > 質問の最初の実行結果だと、asyncの冒頭が同期処理になっててstartのイベントが登録される前にイベントが発行されてconsole.logで表示されない console.logは正常に動作してはいるんですが、 意図したタイミングまで遅延してくれてないって意味ですよね? それで合っています。 > 冒頭から非同期で、非同期処理そのものが後回しにされるので、startが表示されるようになるということですよね? これもそれで合ってます。 > 同期処理Fastで、非同期処理 = 後回し のイメージでいいですかね? そうですね。 Node.jsの場合、JSで自分で書いたコードは基本的に全て同期処理 C++等のモジュールの外部の力を狩りて、 CPU以外の何か別の要因がボトルネックになると考えられる処理を利用させて頂く場合は非同期処理になる。 こういう認識が良いでしょう。 今回の質問は自分からEventEmitterを使ったり、Promiseを使って非同期にしてますね。 これは本来の非同期の用途として考えた場合は悪手になりやすいです。 (PubSub的な仕組みを作る時は心強いですが、本当に必要なのか?という視点も大事)
Patao150205

2021/04/17 03:48

わかりました。読んでみます! ご丁寧にありがとうございました。
guest

あなたの回答

tips

太字

斜体

打ち消し線

見出し

引用テキストの挿入

コードの挿入

リンクの挿入

リストの挿入

番号リストの挿入

表の挿入

水平線の挿入

プレビュー

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

ただいまの回答率
85.50%

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

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

質問する

関連した質問