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

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

ただいまの
回答率

90.87%

  • JavaScript

    14222questions

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

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

受付中

回答 3

投稿 編集

  • 評価
  • クリップ 0
  • VIEW 153

miga

score 1

 前提・実現したいこと

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ページの「アクティブ」「注目」タブのフィードに表示されにくくなります。

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

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

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

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

    詳細な説明はこちら

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

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

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

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

  • 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のイベントループ機構について調べてみると良いよ

    キャンセル

回答 3

+5

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のようなマルチスレッド機構を用いることになると思います。

投稿

編集

  • 回答の評価を上げる

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

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

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

  • 回答の評価を下げる

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

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

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

  • 2018/04/18 08:03

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

    キャンセル

  • 2018/04/18 13:37 編集

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

投稿

編集

  • 回答の評価を上げる

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

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

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

  • 回答の評価を下げる

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

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

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

  • 2018/04/17 19:50

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

    キャンセル

  • 2018/04/18 08:00

    > n分かかる処理」の内容が不明
    分数は適当でしたが、例えばAPIを利用する場合などの少し時間がかかる処理を想定しています。

    >「Promiseを通すことで非同期処理化する」と誤解していませんか。
    Promiseで非同期処理化されるという認識ですが、違うのですか?

    キャンセル

  • 2018/04/18 08:49

    親記事に追記しました。

    キャンセル

  • 2018/04/18 11:12

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

    キャンセル

  • 2018/04/18 13:40

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

    キャンセル

  • 2018/04/18 13:54

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

    キャンセル

+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)
        });
    });
  });

投稿

  • 回答の評価を上げる

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

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

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

  • 回答の評価を下げる

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

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

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

  • 2018/04/18 07:54 編集

    >非同期の処理を単に実行しているだけですから処理が短い方から
    順次出力されますよね?
    この認識です。

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

    キャンセル

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

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

関連した質問

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

  • JavaScript

    14222questions

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