JavaScript 非同期処理とforを組み合わせた書き方が分からない

解決済

回答 2

投稿 編集

  • 評価
  • クリップ 1
  • VIEW 3,911

Takiro

score 5

前提

今、JavaScriptの非同期処理について学んでいます。
for文と非同期処理をうまく書く方法を探していたところ、とあるウェブサイトで以下のようなコードを見つけました。
(参考URL:javascriptで非同期処理をfor文で回したい。)

しかし、内容が理解出来ません。

初心者なので、当たり前のことを理解していないとんちんかんな質問になっているかもしれませんが、どうぞよろしくお願いします。

例えば、次のような非同期処理を上から順に行って行きたい時(a,b,c,dを順番に表示したい)、下のように書き換えればa,b,c,dの順に表示されるようになります。

setTimeout( () =>{console.log('a');},4000);
setTimeout( () =>{console.log('b');},3000);
setTimeout( () =>{console.log('c');},2000);
setTimeout( () =>{console.log('d');},1000);
(function(){
var arr = ['a','b','c','d'];
var d = Promise.resolve();
for(var i=0;i<arr.length;i++){
 d = d.then( function(i){
    return new Promise((resolve,reject)=>{
      setTimeout(()=>{
        console.log(arr[i]);
        resolve();
      }
      ,(4-i)*1000)
    })
  }.bind(null,i))
}
})();

疑問点

①Promise.resolve()は、then()メソッドを呼び出すためにPromiseオブジェクトを返す役割を果たしていると解釈してよいのでしょうか。
②forを展開した時にどういう記述になるのか分かりません。あるいは、forが進むごとにどのような処理が具体的に行われているのか分かりません。d = d.then(以下略)とすることでiのカウントが進むごとにthenチェーンがつながるのかな程度の想像で理解が止まってしまいます(そもそもその想像自体がまちがっているかもしれませんが)。
③即時関数への引数をbindで指定していますが、なぜそうする必要があるのかが分かりません。実際、.bind(null, i)の部分を(i)に変えたところ、d,c,b,aの順に表示されてしまいました。コールバック関数の引数を固定する場合bindを使うようですが、そもそもその理由が理解できていません。bindの基本的な使い方としてthisの対象を固定するというのは理解したのですが、引数について分からないままです……。

以上の点、お答え頂ける優しい方がいたらどうかよろしくお願いします。

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

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

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

    クリップを取り消します

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

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

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

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

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

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

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

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

    質問の評価を下げる

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

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

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

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

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

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

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

    詳細な説明はこちら

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

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

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

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

  • think49

    2017/05/17 19:43

    引用元(URL)を開示して下さい。また、引用文と質問者さん本人の文章の見分けがつかないので、引用箇所は行頭に "> " を付けてmarkdown記法で書いて頂けると、読む人の誤解を生まなくて良いと思います。

    キャンセル

  • Takiro

    2017/05/17 21:05

    大変失礼しました。引用元を明記し、引用箇所に>をつけました。ご指摘ありがとうございました。

    キャンセル

  • think49

    2017/05/17 22:43

    ``` の行頭にも "> " を付けて下さい。そうすれば、blockquote>pre>code でマークアップされます。

    キャンセル

  • Takiro

    2017/05/17 22:56

    引用箇所の修正を行いました。引用の方法について大変参考になりました。追加のご指摘ありがとうございました。

    キャンセル

回答 2

+2

 再帰処理 (Promise + setTimeout)

- ES6 Promiseの場合
 

(function(){
var arr = ['a','b','c','d'];
var d = Promise.resolve();
for(var i=0;i<arr.length;i++){
  d = d.then( function(i){
    return new Promise((resolve,reject)=>{
      setTimeout(()=>{
        console.log(arr[i]);
        resolve();
      }
      ,(4-i)*1000)
    })
  }.bind(null,i))
}
})();


  - javascriptで非同期処理をfor文で回したい。 - イノベートな非日常

元記事のコードをリファクタリングすると、次のコードになります。

let p = Promise.resolve();
for (const [key, value] of ['a','b','c','d'].entries()) {
  p = p.then((resolve) => new Promise((resolve) => {
    return setTimeout((value) => {
      console.log(value);
      resolve();
    }, 4000 - 1000 * key, value);
  }));
}

p = p.then(); で代入を繰り返す事で p.then().then().then().then() を実現しています。

 再帰処理 (setTimeout)

しかしながら、この例なら Promise を使うまでもないと私は思います。

setTimeout(function handleTimeout (array, index) {
  console.log(array[index++]);

  if (index < array.length) {
    setTimeout(handleTimeout, 4000 - 1000 * index, array, index);
  }
}, 4000, ['a','b','c','d'], 0);

Re: Takiro さん

投稿

編集

  • 回答の評価を上げる

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

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

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

  • 回答の評価を下げる

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

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

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

  • 2017/05/17 23:32

    質問の本題とは関係なさそうですが、setTimeout の第二引数が元コードと同一の挙動ではなかったので修正しました。

    キャンセル

  • 2017/05/18 12:49

    ご回答ありがとうございます。配列をイテレーターオブジェクトにしてfor...ofというこのような書き方が可能になるのですね。かなり見やすくなるので参考になりました。今後この書き方をしてみたいと思います。
    それとコールバック関数を使った再帰の方法ですが、確かに簡単なものならpromiseを使わずこちらの方がよさそうです。

    キャンセル

checkベストアンサー

0

①Promise.resolve()は、then()メソッドを呼び出すためにPromiseオブジェクトを返す役割を果たしていると解釈してよいのでしょうか。 

はい

②forを展開した時に...

ご想像の通り、then をつなげているだけです。
bind でわかりづらくなっていますが、

d = d.then(() => new Promise((resolve, reject) => {setTimeout うんぬん})

です。

③即時関数への引数をbindで指定していますが、...

bind は元の関数のカリー化された新しい関数を作成して返すものです。第一引数は this, それ以降は関数の引数に紐付きます。

const fun1 = function(x, y) {
    console.log(this, x, y);
};

const fun2 = fun1.bind(1, 2, 4);
fun2(); // [Number: 1] 2 4  

このようになります。

そのため

function(i){...}.bind(null, i) // () => {...} thisはnull

となります。

投稿

  • 回答の評価を上げる

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

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

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

  • 回答の評価を下げる

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

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

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

  • 2017/05/17 21:33 編集

    ご回答ありがとうございます。分かりやすい説明で大変助かりました。
    ただどうしても気になる点が残ってしまいます。
    もし時間に余裕があればでいいので、お答えいただければと思います。
    ②について:thenがつながっていく処理をしていることは分かりましたが、d = d.thenのdの部分がなかなか理解出来ません。なぜ変数に処理を入れてその部分で実行されるのか、というのが疑問点です。
    感覚としては、関数式の定義みたいな感じなのでしょうか。
    var d = function(){}; //ここでdという関数を定義
    d(); //dを実行
    上の例で言えば、
    d = d.then(以下略)がdを実行する部分ということになるのでしょうか。
    ③について:bindで引数を固定するというのは分かりましたが、.bind(null, i)の部分を(i)にして即時関数の書き方にした場合挙動が変わってしまうのはなぜでしょうか。

    初歩的な質問ばかりかと思うので、参考となるURLを貼っていただけるだけでもありがたいです。
    よろしくお願いします。

    キャンセル

  • 2017/05/17 22:15

    > d = d.then(以下略)がdを実行する部分ということになるのでしょうか。
    いいえ。
    d = d.then(...) で作られるのは、
    Promise.resolve().then(...).then(...).then(...).then(...)
    のようなオブジェクトです。実行はまだしません。

    > 即時関数
    https://developer.mozilla.org/ja/docs/Web/JavaScript/Reference/Global_Objects/Promise/then
    即時関数にすると、thenの第一引数が関数ではなくなるため
    > 成功状態の Promise を作成して返します。
    とあります。(i)とした即時関数がPromiseを返しても、それを待たず成功状態の Promiseでthenをつなぐのでおかしくなります。

    キャンセル

  • 2017/05/17 22:40

    分かりやすいご回答、重ね重ねありがとうございます。d = d.then(...)の部分と即時関数に関して、すっきりと理解することが出来ました。
    しかし、ここでまた疑問が生じてしまいます。Promise.resolve().then(...).then(...).then(...).then(...)がfor文によって再帰的にdに代入された後、この部分は一体どこで実行されるのでしょうか。

    質問が何度にもわたってしまって申し訳ありません。

    キャンセル

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

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