Node.js: 非阻塞代码

Node.js: non-blocking code

我刚开始使用 Node.js 并努力研究非阻塞(异步?)代码的一些细节。我知道已经有很多关于阻塞代码和非阻塞代码的问题,但在阅读了其中一些之后,我仍然无法解决这个问题。

作为学习练习,我制作了一个简单的脚本,它从文件中加载 URLs,使用 request 模块查询它们,并在 URL 是 URL 时通知我纽约时报主页。

这是一个 MWE:

// CSV Parse test
'use strict';

var request = require('request');
var fs = require('fs');
var parse = require('csv-parse');

var text = fs.readFileSync('input.txt','utf8');
var r_isnyt = /New York Times/; 

var data = [];

parse(text, {skip_empty_lines: true}, function(err, data){

    for (var r = 0; r < data.length; r++) {
        console.log ('Logging from within parse function:');
        console.log ('URL: '+data[r][0]+'\n');

        var url = data[r][0];

        request(url, function(error, response, body) {
            console.log ('Logging from within request function:');
            console.log('Loading URL: '+url+'\n');
            if (!error && response.statusCode == 200) {
                if (r_isnyt.exec(body)){ 
                    console.log('This is the NYT site! ');
                }
                console.log ('');
            }
        });         
    }
});

这是我的 input.txt:

http://www.nytimes.com/
www.google.com

根据我对非阻塞代码的理解,这个程序的流程是:

这是绕过我的问题的冗长方式。我只是想澄清一下,我认为我了解非阻塞代码的基本概念。所以让我感到困惑的是我的输出如下:

>node parsecsv.js
Logging from within parse function:
URL: http://www.nytimes.com/

Logging from within parse function:
URL: www.google.com

Logging from within request function:
Loading URL: www.google.com

Logging from within request function:
Loading URL: www.google.com

This is the NYT site!

>

我理解为什么 request 打印输出都在最后一起出现,但为什么它们都打印 Google,更莫名其妙的是,为什么最后一个说它是纽约时报网站,当它前面的日志行(来自同一个 request 调用)刚刚打印 Google 时?这就像 request 调用获得正确的 URLs,但 console.log 调用滞后,并且只在末尾打印所有内容和结束值。

有趣的是,如果我颠倒 URL 的顺序,输出中的一切看起来都是正确的,我猜是因为站点的响应时间不同:

node parsecsv.js
Logging from within parse function:
URL: www.google.com

Logging from within request function:
Loading URL: www.google.com

Logging from within parse function:
URL: http://www.nytimes.com/

Logging from within request function:
Loading URL: http://www.nytimes.com/

This is the NYT site!

>

提前致谢。

更新

根据下面 jfriend00 的回答,我更改了我的代码以使用 .forEach 循环,如下所示。这似乎可以解决问题。

// CSV Parse test
'use strict';

var request = require('request');
var fs = require('fs');
var parse = require('csv-parse');

var text = fs.readFileSync('input.txt','utf8');
var r_isnyt = /New York Times/; 

var data = [];

parse(text, {skip_empty_lines: true}, function(err, data){

    data.forEach( function(row) {
        console.log ('Logging from within parse function:');
        console.log ('URL: '+row[0]+'\n');

        let url = row[0];

        request(url, function(error, response, body) {
            console.log ('Logging from within request function:');
            console.log('Loading URL: '+url+'\n');
            if (!error && response.statusCode == 200) {
                if (r_isnyt.exec(body)){ 
                    console.log('This is the NYT site! ');
                }
                console.log ('');
            }
        });         
    });
});

您的代码很好,并且您对它的工作方式是正确的(包括响应时间的差异是什么让您切换顺序时一切看起来都很好),但是您的日志记录已成为意外关闭的牺牲品: urlparse() 回调的范围内声明和更新,并且在两次记录 www.google.com 的情况下,它会通过 [= 之前​​的循环更新为其最终值=13=] 回调开始执行。

I understand why the request printouts all happen together at the end, but why do they both print Google, and much more baffling, why does the last one say it's the NYT site, when the log line right before it (from within the same request call) has just printed Google? It's like the request calls are getting the correct URLs, but the console.log calls are lagging, and just print everything at the end with the ending values.

您正确理解 for 循环启动所有 request() 调用,然后它们稍后以响应返回的顺序完成。

但是,您的日志记录语句:

console.log('Loading URL: '+url+'\n');

指的是 for 循环中的一个变量,它由 for 循环的所有迭代共享。因此,由于 for 循环运行完成,然后稍后所有响应到达并得到处理,您的 for 循环将在任何响应得到处理时完成,因此变量 [=当 for 循环结束时,20=] 将具有它所具有的任何值,这将是 for 循环的最后一次迭代的值。

在 ES6 中,您可以使用 let 而不是 var 定义变量,它将是块作用域,因此循环的每次迭代都会有一个唯一的变量 url .

所以,改变:

var url = data[r][0];

let url = data[r][0];

在 ES6 之前,避免此问题的一种常见方法是使用 .forEach() 进行迭代,因为它需要一个回调函数,因此根据 [=26= 的性质,您的所有循环代码都在其自己的范围内] 有效,因此每次迭代都有自己的局部变量而不是共享局部变量。


仅供参考,虽然 let 解决了这个问题并且是它的设计目的之一,但我认为如果您只使用 .forEach() 进行迭代,您的代码可能会更清晰一些,因为它会将对 data[r] 的多个引用替换为对当前数组迭代值的单个引用。

parse(text, {skip_empty_lines: true}, function(err, data){

    data.forEach( function(row) {
        console.log ('Logging from within parse function:');
        console.log ('URL: '+row[0]+'\n');

        let url = row[0];

        request(url, function(error, response, body) {
            console.log ('Logging from within request function:');
            console.log('Loading URL: '+url+'\n');
            if (!error && response.statusCode == 200) {
                if (r_isnyt.exec(body)){ 
                    console.log('This is the NYT site! ');
                }
                console.log ('');
            }
        });         
    });
});