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
根据我对非阻塞代码的理解,这个程序的流程是:
parse(text, {skip_empty_lines: true}, function(err, data){
加载数据和 returns 二维数组中的输入文件的行,该行在该行之后是完整且可用的。
For
循环遍历它,用行 request(url, function(error, response, body) {
加载 URLs,这是非阻塞的(对吧?),所以 For 循环继续而不等待前一个 URL 完成加载。
因此,您可以同时加载多个 URL,并且 request
中的 console.log
调用将按响应顺序打印收到,而不是输入文件的顺序。
在 request
中,它可以访问 url
的请求结果,我们打印 URL,检查它是否是纽约时报,并打印该检查的结果(我认为所有阻塞步骤)。
这是绕过我的问题的冗长方式。我只是想澄清一下,我认为我了解非阻塞代码的基本概念。所以让我感到困惑的是我的输出如下:
>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 ('');
}
});
});
});
您的代码很好,并且您对它的工作方式是正确的(包括响应时间的差异是什么让您切换顺序时一切看起来都很好),但是您的日志记录已成为意外关闭的牺牲品: url
在 parse()
回调的范围内声明和更新,并且在两次记录 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 ('');
}
});
});
});
我刚开始使用 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
根据我对非阻塞代码的理解,这个程序的流程是:
parse(text, {skip_empty_lines: true}, function(err, data){
加载数据和 returns 二维数组中的输入文件的行,该行在该行之后是完整且可用的。For
循环遍历它,用行request(url, function(error, response, body) {
加载 URLs,这是非阻塞的(对吧?),所以 For 循环继续而不等待前一个 URL 完成加载。因此,您可以同时加载多个 URL,并且
request
中的console.log
调用将按响应顺序打印收到,而不是输入文件的顺序。在
request
中,它可以访问url
的请求结果,我们打印 URL,检查它是否是纽约时报,并打印该检查的结果(我认为所有阻塞步骤)。
这是绕过我的问题的冗长方式。我只是想澄清一下,我认为我了解非阻塞代码的基本概念。所以让我感到困惑的是我的输出如下:
>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 ('');
}
});
});
});
您的代码很好,并且您对它的工作方式是正确的(包括响应时间的差异是什么让您切换顺序时一切看起来都很好),但是您的日志记录已成为意外关闭的牺牲品: url
在 parse()
回调的范围内声明和更新,并且在两次记录 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 ('');
}
});
});
});