while(count!==3) fail to check callback state

2019-09-02 08:11发布

I was learning Node with a tutorial called learnyounode from NodeSchool. This is about one of the 13 questions it provided: send 3 http get requests to 3 urls indicated by first 3 command line arguments, print out the responses in the order of urls when all the response chunks are collected.
I came up with this code snippet:

var http = require("http");
var count = 0;
var strArr = ["","",""];
getData(0);
getData(1);
getData(2);
while(count!==3);
console.log(strArr[0]);
console.log(strArr[1]);
console.log(strArr[2]);

function getData(i) {
    http.get(process.argv[i+2], function (response) {
        response.setEncoding("utf8");
        response.on("data", function (data) {
            strArr[i] += data;
        });
        response.on("end", function (data) {
            count++;
        });
    });
}

I was expecting the while loop to hold back the print statements for me until count turns 3, that is, all 3 responses are gathered completely. However, it didn't work as I expected. Also, I put a print statement in the while loop and it showed that count would always be 0.
I then peeked the answer and learned that a way around is to check the value of count in the callback for response.on("end", ...), like below:

var http = require("http");
var count = 0;
var strArr = ["","",""];
getData(0);
getData(1);
getData(2);

function getData(i) {
    http.get(process.argv[i+2], function (response) {
        response.setEncoding("utf8");
        response.on("data", function (data) {
            strArr[i] += data;
        });
        response.on("end", function (data) {
            count++;
            if(count===3) {
                console.log(strArr[0]);
                console.log(strArr[1]);
                console.log(strArr[2]);
            }
        });
    });
}

This way, I did pass the test, but why the while-loop method didn't work out still puzzles me.
Thanks in advance for anyone who looks at this.

1条回答
Melony?
2楼-- · 2019-09-02 09:01

JavaScript is single-threaded. It executes each execution context until it is finished, then it checks with the event loop to see if there are any new execution contexts queued up that it should execute (such as the callback of an asynchronous function).

The three getData calls all return immediately, then the while loop executes in the thread. The callbacks to http.get cannot execute until the current execution context is finished (until the while loop and everything after it have executed), so there is no way for count to increase, and no way for the loop to end.

The solution you have found works well, but to help with understanding you should realize that setTimeout and setInterval are asynchronous, so they do not block the thread. You could have solved this with something like:

getData(0);
getData(1);
getData(2);

setTimeout( function check_count ( ) { 
    if ( count !== 3 )
        return setTimeout( check_count, 100 );

    console.log(strArr[0]);
    console.log(strArr[1]);
    console.log(strArr[2]);

}, 100 );

That's not a nice solution, since it is arbitrarily checking every 100 ms instead of just waiting until the third callback executes and then immediately logging the results. It is just a demonstatrion of how you can "loop" without blocking the thread.

查看更多
登录 后发表回答