NodeShoolでわからない問題があります。


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]

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




最後から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`等を学ぶと得られると思います。




最後から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`等を学ぶと得られると思います。

