前提・実現したいこと
Promiseを使って以下のようなコードを書いたとき、処理1-3の結果はどのような順番で表示されるかを知りたいです。それぞれの処理は完了するまで、コメントアウトに記載している時間が掛かる前提です。
該当のソースコード
var func1 = function(){
return new Promise(function(resolve){
var sum = 1+1; //処理1 (15分掛かる処理)
resolve(sum);
})
}
var func2 = function(){
return new Promise(function(resolve){
var sum = 2+2; //処理2 (10分掛かる処理)
resolve(sum);
})
}
var func3 = function(){
return new Promise(function(resolve){
var sum = 3+3; //処理3 (5分掛かる処理)
resolve(sum);
})
}
func1().then(function(sum){console.log(sum)});
func2().then(function(sum){console.log(sum)});
func3().then(function(sum){console.log(sum)});
fulfilledになったものから実行される認識なので、以下のように出力されると考えています。
処理3
処理2
処理1
ただ、上記のコードの最後の部分を以下のように順番を変えて、実行すると、このような結果になりましたので、処理結果の出力順は、Promiseを呼び出す順番によって決まるのかなと思ってしまいました。
func2().then(function(sum){console.log(sum)});
func1().then(function(sum){console.log(sum)});
func3().then(function(sum){console.log(sum)});
処理2
処理1
処理3
Re:think49さん
Promise内でコールバック関数を使わないと非同期処理にならないとのことで、上記コードが非同期にならない理由がわかりました。以下のようにすれば期待する結果が得られるでしょうか。コメントアウトに記述した時間だけ処理に時間が掛かると仮定として考えてください。
var func1 = function(){
return new Promise(function(resolve){
setTimeout(function(){
var sum = 1+1; //処理1 (15分掛かる処理)
resolve(sum);
},0);
})
}
var func2 = function(){
return new Promise(function(resolve){
setTimeout(function(){
var sum = 2+2; //処理2 (10分掛かる処理)
resolve(sum);
},0);
})
}
var func3 = function(){
return new Promise(function(resolve){
setTimeout(function(){
var sum = 3+3; //処理3 (5分掛かる処理)
resolve(sum);
},0);
})
}
func2().then(function(sum){console.log(sum)});
func1().then(function(sum){console.log(sum)});
func3().then(function(sum){console.log(sum)});
期待する結果
6 //5分後
4 //10分後
2 //15分後
-
気になる質問をクリップする
クリップした質問は、後からいつでもマイページで確認できます。
またクリップした質問に回答があった際、通知やメールを受け取ることができます。
クリップを取り消します
-
良い質問の評価を上げる
以下のような質問は評価を上げましょう
- 質問内容が明確
- 自分も答えを知りたい
- 質問者以外のユーザにも役立つ
評価が高い質問は、TOPページの「注目」タブのフィードに表示されやすくなります。
質問の評価を上げたことを取り消します
-
評価を下げられる数の上限に達しました
評価を下げることができません
- 1日5回まで評価を下げられます
- 1日に1ユーザに対して2回まで評価を下げられます
質問の評価を下げる
teratailでは下記のような質問を「具体的に困っていることがない質問」、「サイトポリシーに違反する質問」と定義し、推奨していません。
- プログラミングに関係のない質問
- やってほしいことだけを記載した丸投げの質問
- 問題・課題が含まれていない質問
- 意図的に内容が抹消された質問
- 過去に投稿した質問と同じ内容の質問
- 広告と受け取られるような投稿
評価が下がると、TOPページの「アクティブ」「注目」タブのフィードに表示されにくくなります。
質問の評価を下げたことを取り消します
この機能は開放されていません
評価を下げる条件を満たしてません
質問の評価を下げる機能の利用条件
この機能を利用するためには、以下の事項を行う必要があります。
- 質問回答など一定の行動
-
メールアドレスの認証
メールアドレスの認証
-
質問評価に関するヘルプページの閲覧
質問評価に関するヘルプページの閲覧
+6
fulfilledになったものから実行される認識
それで合っていると思います。
処理結果の出力順は、Promiseを呼び出す順番によって決まるのかな
そんなはずはないと思います。書いておられる処理自体に何か依存関係があって実行タイミングがずれたりするのではないでしょうか?
setTimeoutを使って実験してみるとPromise自体の動きを確認しやすいと思います。
ところで普通の計算機のtimeoutの分解能はせいぜい10msecぐらいですので、実験するにしてもあまり短期間の非同期処理だと順番が乱れるかもしれません。下記の程度なら15, 10, 5にしても動くようでしたが。
function f(to) {
return new Promise((resolve, reject) => {
setTimeout(() => resolve(to), to)
})
}
f(150).then(r => console.log(r))
f(100).then(r => console.log(r))
f( 50).then(r => console.log(r))
setTimeout(() => {
f(100).then(r => console.log(r)) // 順番を変えてみる
f(150).then(r => console.log(r)) // 順番を変えてみる
f( 50).then(r => console.log(r))
}, 1000)
結果
50
100
150
50
100
150
追記:短く書くためにES2015で書いてます。最初の回答でvar f = (to) => {...}
としましたが特に意図あってのことではなかったのでfを普通のfunction定義に置き換えました。
追記: Javascriptで書いた部分は(Workerを使って意識的に非同期なスレッドで行わない限り)必ず同期的に行われます。Promiseは非同期に何かをしてくれる機構ではなく、非同期に行われる処理が完了してから次のことをするための同期機構にしか過ぎないということが伺えるような例を書いてみました。
const t0 = +new Date()
function log(msg) {
const elapsed = " " + (+new Date() - t0)
const prefix = elapsed.slice(elapsed.length - 4)
console.log(`${prefix}: ${msg}`)
}
function f(msg, sy, to) {
log(`invoked ${msg}(${sy} ${to})`)
return new Promise((resolve, reject) => {
if (sy == 'sync') {
const start = +new Date()
do {} while (+new Date() - start <= to)
doResolve()
} else {
setTimeout(() => doResolve(), to)
}
function doResolve() {
resolve(`${msg} ${sy} ${to}`)
log(`resolved ${msg} ${sy} ${to}`)
}
})
}
function done(r) {
log(`promise chain done ${r}`)
}
f('A', 'async', 0).then(r => f(`${r}->then`, 'async', 0)).then(done)
f('B', 'async', 0).then(r => f(`${r}->then`, 'async', 0)).then(done)
f('C', 'async', 100).then(r => f(`${r}->then`, 'async', 200)).then(done)
f('D', 'async', 200).then(r => f(`${r}->then`, 'async', 100)).then(done)
f('--', 'async', 1000)
.then(r => {
f('A', 'sync', 0).then(r => f(`${r}->then`, 'sync', 0)).then(done)
f('B', 'sync', 0).then(r => f(`${r}->then`, 'sync', 0)).then(done)
f('C', 'sync', 100).then(r => f(`${r}->then`, 'sync', 200)).then(done)
f('D', 'sync', 200).then(r => f(`${r}->then`, 'sync', 100)).then(done)
})
結果
1: invoked A(async 0)
2: invoked B(async 0)
2: invoked C(async 100)
2: invoked D(async 200)
<--本来の非同期処理の場合、非同期処理の起動は一気に行われる
2: invoked --(async 1000)
2: resolved A async 0
2: resolved B async 0
2: invoked A async 0->then(async 0)
2: invoked B async 0->then(async 0)
2: resolved A async 0->then async 0
2: resolved B async 0->then async 0
2: promise chain done A async 0->then async 0
2: promise chain done B async 0->then async 0
<--C/Dなど時間がかかる処理があってもそれを非同期に行いさえすればA/BはC/Dの完了前に完了する
<--これはPromiseによる恩恵ではない。A/B/C/Dの処理(setTimeout)が非同期であることがポイント
112: resolved C async 100
112: invoked C async 100->then(async 200)
206: resolved D async 200
206: invoked D async 200->then(async 100)
315: resolved D async 200->then async 100
315: promise chain done D async 200->then async 100
315: resolved C async 100->then async 200
315: promise chain done C async 100->then async 200
1008: resolved -- async 1000
1008: invoked A(sync 0)
1024: resolved A sync 0
1024: invoked B(sync 0)
1039: resolved B sync 0
1039: invoked C(sync 100)
1149: resolved C sync 100
1149: invoked D(sync 200)
<-- Cは同期的に(ビジーループで)100ms消費するためDはその後に起動される(全然非同期でない)
1359: resolved D sync 200
1359: invoked A sync 0->then(sync 0)
<-- C,Dが同期的に300ms処理しているためAがとっくに終わっていても
A/Bのthen部分はC/Dの起動後にしか動けない
1375: resolved A sync 0->then sync 0
1375: invoked B sync 0->then(sync 0)
1391: resolved B sync 0->then sync 0
1391: invoked C sync 100->then(sync 200)
1594: resolved C sync 100->then sync 200
1594: invoked D sync 200->then(sync 100)
<-- Dのthen部分は1359ms後にresolveされている。しかしCのthen部分を同期的に処理しているため
それが終わらないとDのthen部分が起動できない
Dのthen部分はresolveされてから250msも後になってからでないと起動されない(全然非同期でない)
1703: resolved D sync 200->then sync 100
1703: promise chain done A sync 0->then sync 0
1703: promise chain done B sync 0->then sync 0
1703: promise chain done C sync 100->then sync 200
1703: promise chain done D sync 200->then sync 100
<-- 肝心の処理が同期的になっているとPromiseを使ってもなんら意味がない
A-Bの処理が全て終わってからでないとpromise chainが完了しないことからもそれがわかる
Javascriptで記述するのは「非同期処理を起動するのに必要なパラメーター計算と非同期処理の起動」「非同期処理が完了した際に結果を料理すること」であり、どちらも一瞬で終わるべき処理内容とするのが普通だと思います。それは基本的に非同期APIを用いて論理を書くからです。もし同期APIを用いて逐次的に処理するような機能を並行して動かしたいのならthink49さんが回答しておられるようにWorkerのようなマルチスレッド機構を用いることになると思います。
投稿
-
回答の評価を上げる
以下のような回答は評価を上げましょう
- 正しい回答
- わかりやすい回答
- ためになる回答
評価が高い回答ほどページの上位に表示されます。
-
回答の評価を下げる
下記のような回答は推奨されていません。
- 間違っている回答
- 質問の回答になっていない投稿
- スパムや攻撃的な表現を用いた投稿
評価を下げる際はその理由を明確に伝え、適切な回答に修正してもらいましょう。
+4
一応、一つの可能性として。
同期処理
「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 さん
投稿
-
回答の評価を上げる
以下のような回答は評価を上げましょう
- 正しい回答
- わかりやすい回答
- ためになる回答
評価が高い回答ほどページの上位に表示されます。
-
回答の評価を下げる
下記のような回答は推奨されていません。
- 間違っている回答
- 質問の回答になっていない投稿
- スパムや攻撃的な表現を用いた投稿
評価を下げる際はその理由を明確に伝え、適切な回答に修正してもらいましょう。
+2
非同期の処理を単に実行しているだけですから処理が短い方から
順次出力されますよね?
ちょっとPromiseを誤解されているのでは?
非同期処理を順に実効するならたとえばこう
var func1 = function(){
return new Promise(function(resolve,reject){
setTimeout(function(){
var sum = 1+1;
resolve(sum);
},1500);
});
}
var func2 = function(){
return new Promise(function(resolve){
setTimeout(function(){
var sum = 2+2;
resolve(sum);
},1000);
});
}
var func3 = function(){
return new Promise(function(resolve){
setTimeout(function(){
var sum = 3+3;
resolve(sum);
},500);
});
}
func2().then(function(sum){
console.log(sum);
func1().then(function(sum){
console.log(sum);
func3().then(function(sum){
console.log(sum)
});
});
});
投稿
-
回答の評価を上げる
以下のような回答は評価を上げましょう
- 正しい回答
- わかりやすい回答
- ためになる回答
評価が高い回答ほどページの上位に表示されます。
-
回答の評価を下げる
下記のような回答は推奨されていません。
- 間違っている回答
- 質問の回答になっていない投稿
- スパムや攻撃的な表現を用いた投稿
評価を下げる際はその理由を明確に伝え、適切な回答に修正してもらいましょう。
15分調べてもわからないことは、teratailで質問しよう!
- ただいまの回答率 88.10%
- 質問をまとめることで、思考を整理して素早く解決
- テンプレート機能で、簡単に質問をまとめられる
質問への追記・修正、ベストアンサー選択の依頼
HayatoKamono
2018/04/17 19:16 編集
最後の実行結果は15分なり10分なりかかる処理が含まれるコードの実行結果ですか?
miga
2018/04/17 19:21
いえ、単純な計算式です。
x_x
2018/04/18 13:33
非同期処理がではなく、並列処理がしたいのでは?
defghi1977
2018/04/18 14:04
JavaScriptのイベントループ機構について調べてみると良いよ