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

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

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

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

Q&A

3回答

359閲覧

Promiseで非同期処理をする場合の挙動を知りたい

miga

総合スコア7

JavaScript

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

0グッド

0クリップ

投稿2018/04/17 10:08

編集2018/04/18 02:12

前提・実現したいこと

Promiseを使って以下のようなコードを書いたとき、処理1-3の結果はどのような順番で表示されるかを知りたいです。それぞれの処理は完了するまで、コメントアウトに記載している時間が掛かる前提です。

該当のソースコード

javascript

1var func1 = function(){ 2 return new Promise(function(resolve){ 3 var sum = 1+1; //処理1 (15分掛かる処理) 4 resolve(sum); 5 }) 6} 7 8var func2 = function(){ 9 return new Promise(function(resolve){ 10 var sum = 2+2; //処理2 (10分掛かる処理) 11 resolve(sum); 12 }) 13} 14 15var func3 = function(){ 16 return new Promise(function(resolve){ 17 var sum = 3+3; //処理3 (5分掛かる処理) 18 resolve(sum); 19 }) 20} 21 22func1().then(function(sum){console.log(sum)}); 23func2().then(function(sum){console.log(sum)}); 24func3().then(function(sum){console.log(sum)});

fulfilledになったものから実行される認識なので、以下のように出力されると考えています。
処理3
処理2
処理1

ただ、上記のコードの最後の部分を以下のように順番を変えて、実行すると、このような結果になりましたので、処理結果の出力順は、Promiseを呼び出す順番によって決まるのかなと思ってしまいました。

javascript

1func2().then(function(sum){console.log(sum)}); 2func1().then(function(sum){console.log(sum)}); 3func3().then(function(sum){console.log(sum)});

処理2
処理1
処理3

Re:think49さん

Promise内でコールバック関数を使わないと非同期処理にならないとのことで、上記コードが非同期にならない理由がわかりました。以下のようにすれば期待する結果が得られるでしょうか。コメントアウトに記述した時間だけ処理に時間が掛かると仮定として考えてください。

javascript

1var func1 = function(){ 2 return new Promise(function(resolve){ 3 setTimeout(function(){ 4 var sum = 1+1; //処理1 (15分掛かる処理) 5 resolve(sum); 6 },0); 7 }) 8} 9 10var func2 = function(){ 11 return new Promise(function(resolve){ 12 setTimeout(function(){ 13 var sum = 2+2; //処理2 (10分掛かる処理) 14 resolve(sum); 15 },0); 16 }) 17} 18 19var func3 = function(){ 20 return new Promise(function(resolve){ 21 setTimeout(function(){ 22 var sum = 3+3; //処理3 (5分掛かる処理) 23 resolve(sum); 24 },0); 25 }) 26} 27 28func2().then(function(sum){console.log(sum)}); 29func1().then(function(sum){console.log(sum)}); 30func3().then(function(sum){console.log(sum)});

期待する結果

6 //5分後
4 //10分後
2 //15分後

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

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

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

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

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

HayatoKamono

2018/04/17 10:16 編集

最後の実行結果は15分なり10分なりかかる処理が含まれるコードの実行結果ですか?
miga

2018/04/17 10:21

いえ、単純な計算式です。
x_x

2018/04/18 04:33

非同期処理がではなく、並列処理がしたいのでは?
defghi1977

2018/04/18 05:04

JavaScriptのイベントループ機構について調べてみると良いよ
guest

回答3

0

fulfilledになったものから実行される認識

それで合っていると思います。

処理結果の出力順は、Promiseを呼び出す順番によって決まるのかな

そんなはずはないと思います。書いておられる処理自体に何か依存関係があって実行タイミングがずれたりするのではないでしょうか?

setTimeoutを使って実験してみるとPromise自体の動きを確認しやすいと思います。

ところで普通の計算機のtimeoutの分解能はせいぜい10msecぐらいですので、実験するにしてもあまり短期間の非同期処理だと順番が乱れるかもしれません。下記の程度なら15, 10, 5にしても動くようでしたが。

javascript

1function f(to) { 2 return new Promise((resolve, reject) => { 3 setTimeout(() => resolve(to), to) 4 }) 5} 6 7f(150).then(r => console.log(r)) 8f(100).then(r => console.log(r)) 9f( 50).then(r => console.log(r)) 10 11setTimeout(() => { 12 f(100).then(r => console.log(r)) // 順番を変えてみる 13 f(150).then(r => console.log(r)) // 順番を変えてみる 14 f( 50).then(r => console.log(r)) 15}, 1000)

結果
50
100
150
50
100
150


追記:短く書くためにES2015で書いてます。最初の回答でvar f = (to) => {...}としましたが特に意図あってのことではなかったのでfを普通のfunction定義に置き換えました。


追記: Javascriptで書いた部分は(Workerを使って意識的に非同期なスレッドで行わない限り)必ず同期的に行われます。Promiseは非同期に何かをしてくれる機構ではなく、非同期に行われる処理が完了してから次のことをするための同期機構にしか過ぎないということが伺えるような例を書いてみました。

javascript

1const t0 = +new Date() 2 3function log(msg) { 4 const elapsed = " " + (+new Date() - t0) 5 const prefix = elapsed.slice(elapsed.length - 4) 6 console.log(`${prefix}: ${msg}`) 7} 8 9function f(msg, sy, to) { 10 log(`invoked ${msg}(${sy} ${to})`) 11 return new Promise((resolve, reject) => { 12 if (sy == 'sync') { 13 const start = +new Date() 14 do {} while (+new Date() - start <= to) 15 doResolve() 16 } else { 17 setTimeout(() => doResolve(), to) 18 } 19 20 function doResolve() { 21 resolve(`${msg} ${sy} ${to}`) 22 log(`resolved ${msg} ${sy} ${to}`) 23 } 24 }) 25 26} 27 28function done(r) { 29 log(`promise chain done ${r}`) 30} 31 32f('A', 'async', 0).then(r => f(`${r}->then`, 'async', 0)).then(done) 33f('B', 'async', 0).then(r => f(`${r}->then`, 'async', 0)).then(done) 34f('C', 'async', 100).then(r => f(`${r}->then`, 'async', 200)).then(done) 35f('D', 'async', 200).then(r => f(`${r}->then`, 'async', 100)).then(done) 36f('--', 'async', 1000) 37.then(r => { 38 f('A', 'sync', 0).then(r => f(`${r}->then`, 'sync', 0)).then(done) 39 f('B', 'sync', 0).then(r => f(`${r}->then`, 'sync', 0)).then(done) 40 f('C', 'sync', 100).then(r => f(`${r}->then`, 'sync', 200)).then(done) 41 f('D', 'sync', 200).then(r => f(`${r}->then`, 'sync', 100)).then(done) 42})

結果

text

1 1: invoked A(async 0) 2 2: invoked B(async 0) 3 2: invoked C(async 100) 4 2: invoked D(async 200) 5 <--本来の非同期処理の場合、非同期処理の起動は一気に行われる 6 2: invoked --(async 1000) 7 2: resolved A async 0 8 2: resolved B async 0 9 2: invoked A async 0->then(async 0) 10 2: invoked B async 0->then(async 0) 11 2: resolved A async 0->then async 0 12 2: resolved B async 0->then async 0 13 2: promise chain done A async 0->then async 0 14 2: promise chain done B async 0->then async 0 15 <--C/Dなど時間がかかる処理があってもそれを非同期に行いさえすればA/BはC/Dの完了前に完了する 16 <--これはPromiseによる恩恵ではない。A/B/C/Dの処理(setTimeout)が非同期であることがポイント 17 112: resolved C async 100 18 112: invoked C async 100->then(async 200) 19 206: resolved D async 200 20 206: invoked D async 200->then(async 100) 21 315: resolved D async 200->then async 100 22 315: promise chain done D async 200->then async 100 23 315: resolved C async 100->then async 200 24 315: promise chain done C async 100->then async 200 251008: resolved -- async 1000 261008: invoked A(sync 0) 271024: resolved A sync 0 281024: invoked B(sync 0) 291039: resolved B sync 0 301039: invoked C(sync 100) 311149: resolved C sync 100 321149: invoked D(sync 200) 33 <-- Cは同期的に(ビジーループで)100ms消費するためDはその後に起動される(全然非同期でない) 341359: resolved D sync 200 351359: invoked A sync 0->then(sync 0) 36 <-- C,Dが同期的に300ms処理しているためAがとっくに終わっていても 37 A/Bのthen部分はC/Dの起動後にしか動けない 381375: resolved A sync 0->then sync 0 391375: invoked B sync 0->then(sync 0) 401391: resolved B sync 0->then sync 0 411391: invoked C sync 100->then(sync 200) 421594: resolved C sync 100->then sync 200 431594: invoked D sync 200->then(sync 100) 44 <-- Dのthen部分は1359ms後にresolveされている。しかしCのthen部分を同期的に処理しているため 45 それが終わらないとDのthen部分が起動できない 46 Dのthen部分はresolveされてから250msも後になってからでないと起動されない(全然非同期でない) 471703: resolved D sync 200->then sync 100 481703: promise chain done A sync 0->then sync 0 491703: promise chain done B sync 0->then sync 0 501703: promise chain done C sync 100->then sync 200 511703: promise chain done D sync 200->then sync 100 52 <-- 肝心の処理が同期的になっているとPromiseを使ってもなんら意味がない 53 A-Bの処理が全て終わってからでないとpromise chainが完了しないことからもそれがわかる

Javascriptで記述するのは「非同期処理を起動するのに必要なパラメーター計算と非同期処理の起動」「非同期処理が完了した際に結果を料理すること」であり、どちらも一瞬で終わるべき処理内容とするのが普通だと思います。それは基本的に非同期APIを用いて論理を書くからです。もし同期APIを用いて逐次的に処理するような機能を並行して動かしたいのならthink49さんが回答しておられるようにWorkerのようなマルチスレッド機構を用いることになると思います。

投稿2018/04/17 10:24

編集2018/04/18 04:09
KSwordOfHaste

総合スコア18394

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

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

miga

2018/04/17 23:03

ありがとうございます。setTimeoutを使うと期待通りの結果を得ていたのですが、単純な計算処理を入れた場合だと、Promiseを呼び出す順番によって出力結果が前後したため、質問させていただきました。 おそらくご指摘の通り、処理時間が短すぎることが原因かと思います。
KSwordOfHaste

2018/04/18 04:37 編集

んー。think49さんyambejpさんや自分の回答は結局は同じ仮定に基づいて答えていますがPromiseを使うことが非同期処理になるのではない点が質問者さんの勘違いしておられる点のようです。非同期処理は「C++で実装された非同期関数」やthnk49さんがコメントしておられるWorkerを使って行うものでしてそれらを使わない場合(Javascriptで記述したコード部分)はあくまで単一スレッドでしか実行されませんので非同期処理ではありません。Promiseの役割はC++(で実装されているAPI)やWorkerで行われる非同期処理の完了の同期を取るための機構であり「非同期に処理を実行する機能」ではないのです。
guest

0

一応、一つの可能性として。

同期処理

「n分かかる処理」の内容が不明ですが、同期処理なら「2->1->3」の順に実行されるのが自然です。
3が出力されるまでに30分かかっていませんか。
「Promiseを通すことで非同期処理化する」と誤解していませんか。

Promiseで非同期処理化されるという認識ですが、違うのですか?

Promiseはコールバック関数によって、非同期処理の実行完了を検知する仕組みです。
同期処理はコードを書いた順に実行されますので、Promiseを通しても実行順は変化しません。

シングルスレッド

また、おそらく勘違いされていることの一つに「非同期処理 !== 並列処理」があります。
JavaScriptはシングルスレッドなので、setTimeoutで非同期処理化してもマルチスレッドにはなりません。
「タイマー処理処理1」実行中に「タイマー処理2」の実行開始時刻を迎えた場合、setTimeoutは「タイマー処理1」が終了するまで、「タイマー処理2」を待機させます。

マルチスレッド化する為には「Service Worker」を使用します。

(2018/04/18 13:34追記)

以下のようにすれば期待する結果が得られるでしょうか。

なりません。
「4(10分後) -> 2(25分後) -> 6(30分後)」が出力されます。
理由は先程説明したように、シングルスレッドだからです。

実際に検証して確かめることをお勧めします。
new Dateをwhileで繰り返せば、n秒待機するsleep関数を作れます。

Re: miga さん

投稿2018/04/17 10:28

編集2018/04/18 04:35
think49

総合スコア18164

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

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

KSwordOfHaste

2018/04/17 10:50

そこには思い至りませんでした。 これが当たりの可能性もありそうな気がしますが、5分かかる同期処理って...
miga

2018/04/17 23:00

> n分かかる処理」の内容が不明 分数は適当でしたが、例えばAPIを利用する場合などの少し時間がかかる処理を想定しています。 >「Promiseを通すことで非同期処理化する」と誤解していませんか。 Promiseで非同期処理化されるという認識ですが、違うのですか?
think49

2018/04/17 23:49

親記事に追記しました。
miga

2018/04/18 02:12

なんとなくわかった気がします。質問を追加しましたので、ご確認頂けますでしょうか。
think49

2018/04/18 04:40

親記事に追記しました。 JavaScriptにおける非同期処理とはユーザの操作を阻害しない意味での非同期です。
think49

2018/04/18 04:54

シングルスレッド節の説明をもう一度、初めから読んでみてください。 期待通りに動かない理由を書いてます。
guest

0

非同期の処理を単に実行しているだけですから処理が短い方から
順次出力されますよね?
ちょっとPromiseを誤解されているのでは?

非同期処理を順に実効するならたとえばこう

javascript

1var func1 = function(){ 2 return new Promise(function(resolve,reject){ 3 setTimeout(function(){ 4 var sum = 1+1; 5 resolve(sum); 6 },1500); 7 }); 8} 9var func2 = function(){ 10 return new Promise(function(resolve){ 11 setTimeout(function(){ 12 var sum = 2+2; 13 resolve(sum); 14 },1000); 15 }); 16} 17var func3 = function(){ 18 return new Promise(function(resolve){ 19 setTimeout(function(){ 20 var sum = 3+3; 21 resolve(sum); 22 },500); 23 }); 24} 25func2().then(function(sum){ 26 console.log(sum); 27 func1().then(function(sum){ 28 console.log(sum); 29 func3().then(function(sum){ 30 console.log(sum) 31 }); 32 }); 33 }); 34

投稿2018/04/17 10:21

yambejp

総合スコア114839

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

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

miga

2018/04/17 22:54 編集

>非同期の処理を単に実行しているだけですから処理が短い方から 順次出力されますよね? この認識です。 別に非同期処理を順に実行したい訳ではありません。あなたのコードだと、全ての処理が終わるまで 3秒ですよね?あくまでPromiseを別個に呼び出した時にどういう結果になるかを質問しています。
guest

あなたの回答

tips

太字

斜体

打ち消し線

見出し

引用テキストの挿入

コードの挿入

リンクの挿入

リストの挿入

番号リストの挿入

表の挿入

水平線の挿入

プレビュー

まだベストアンサーが選ばれていません

会員登録して回答してみよう

アカウントをお持ちの方は

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

ただいまの回答率
85.48%

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

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

質問する

関連した質問