21163


NodeSchool(<http://nodeschool.io/ja/>
)のLearnyounodeの章をやっていてわからないところがあったので質問します。

> ASYNCジャグリング (13問中9問目)  
>  今回の問題は前回のもの（HTTP Collect) とよく似た問題です。http.get() を使って GET リクエスト
> の結果をコンソールに出力するアプリです。  
>  前回のコマンドライン引数は、1つの URL でしたが今回は3つの URL にしてください。  
>  3つの GET リクエストのデータを集め、全てコンソールに書き出してください。  
>  今回の出力には文字カウントは不要です。文字列だけで良いです。  
>  コマンドライン引数 (URL)  
>  1つにつき1つの文字列を出力してください。出力する文字列は、コマンドライン引  
>  数の順番と一致させることを厳守してください。

という問題で模範回答はこちら（<https://github.com/workshopper/learnyounode/blob/master/exercises/juggling_async/solution/solution.js>
）にあります。このコードが正しいというのは理解できます。

知りたいのは私の書いたコードは下記の通りなのですが、最後から２行目のループから抜け出せなくて正しく動きません。

[code]

        var http = require('http');
    
        flag = [];
    
        for(i=2; i<=4; i++){
          http.get(process.argv[i], function(res){
            var body = '';
            res.setEncoding('utf8');
            res.on('data', function(chunk){
              body += chunk;
            });
            res.on('end', function(){
              flag.push(body);
            });
          });
        }
    
        while(flag.length != 3){}
        console.log(flag);
    
[/code]

私のコールバックか変数のスコープの認識に誤りがあるかと思うのですが教えていただけますでしょうか。


CC BY-SA 3.0
2016-01-22T00:35:32.100
21162
2016-01-22T05:57:26.193
14096
question
javascript node.js
NodeShoolでわからない問題があります。


最後から2行目の `while` 文が、イベントループをブロックしているのが原因です。

次のようなループを想像してみてください。

[code]

    var eventQueue = [];
    while (isLiving()) {
        var event = eventQueue.shift();
    
        if (event) {
            event.handle();
        }
    }
    
[/code]

イベントの待ち行列があって、1つのループを回して、待ち行列からイベントを1つずつ取り出して処理しています。  
いろいろ省略していますが、これは Node.js が行っていることです。

ここで、もしも `event.handle();` の中で無限ループを作ってしまったら、次のイベントを取り出して処理できないことが想像できると思います。

@ushijima さんが書いたコード `while(flag.length != 3){}` が行っていることは、まさしく "それ" です。

  1. `while(flag.length != 3){}` ループによって `event.handle();` が終わらないため、次のイベントを処理できません。
  2. 次のイベントを処理できないため、「`http.get` が終わった」というイベントも処理されません。そのため `http.get` のコールバック関数は実行されません。
  3. `http.get` のコールバック関数が実行されないので、`while(flag.length != 3){}` から抜け出すことはできません。そして 1 に戻ります。

例えば次のようにすると、期待通りに動作するでしょう。

[code]

    var http = require('http');
    
    flag = [];
    
    for(i=2; i<=4; i++){
      http.get(process.argv[i], function(res){
        var body = '';
        res.setEncoding('utf8');
        res.on('data', function(chunk){
          body += chunk;
        });
        res.on('end', function(){
          flag.push(body);
          if (flag.length === 3) {
              console.log(flag);
          }
        });
      });
    }
    
[/code]

複数のリクエストを扱うためのより実践的な方法は、この後で`Promise`等を学ぶと得られると思います。


CC BY-SA 3.0
2016-01-22T02:16:49.320
21163
2016-01-22T02:25:11.003
2016-01-22T02:25:11.003
4809
4809
21162
answer


概ね回答はmysticateaさんのものを見てください

間違い方と模範解答を見た結果  
下記のような勘違いをしているのかなぁ？と思ったので補足します。

**ループについて**  
模範解答では下記のようになっております。

[code]

    for (var i = 0; i < 3; i++)
      httpGet(i)
    
[/code]

`{}`を付けない状態のループ(`for`or`while`)や分岐(`IF`)は次の一行をスコープとします。  
ですので一行で次のように書くこともあります  
`for (var i = 0; i < 3; i++) httpGet(i);`  
`function`、`for`、`while`、`if`などのスコープは  
基本的に`{`から、`}`までです。

[code]

    while(flag.length != 3){
        //この中が有効範囲（スコープ）
    }
    
[/code]

* * *

**非同期処理について**  
非同期処理とは、処理をコールバックを待たずに終了する処理です。  
`http.get`がつかわれていますが  
`http.get("対象のURL", "コールバック関数");`です。  
この処理は非同期なので

[code]

    http.get(process.argv[i], function(res){
        console.log("処理１");
    });
    
    console.log("処理２");
    
[/code]

上記の処理を行った際に、`処理１`→`処理２`になる保証は有りません。  
`http.get`で取得次第`処理１`が走るので`処理２`→`処理１`の可能性もあります。

* * *

**追記**  
基本的にループは同期処理です。  
ブラウザによってはループによる処理が発生した場合  
指定秒数以上のループをブロックする機能があります。  
たとえばFireFoxやChromeなどでは一定秒数以上の処理や一定回数以上のループが発生した場合  
処理を強制停止させられます。  
非同期処理の場合はこの限りではありません。  
(  
「リクエスト→(非同期)30秒の待機→処理」は大丈夫  
「リクエスト→(非同期)幾許かの待機→30秒以上かかるループ処理」は中断されます。  
「リクエスト→(非同期)幾許かの待機→１万回以上のループ処理」は中断されます。  
ループ回数の大きな処理は再起処理を非同期で行ったりします。  
)


CC BY-SA 3.0
2016-01-22T02:46:31.917
21165
2016-01-22T05:57:26.193
2016-01-22T05:57:26.193
21162
answer