我刚开始使用Node.js并且正在努力解决一些非阻塞(异步?)代码的细节问题。我知道有很多关于阻塞和非阻塞代码的问题,但在阅读了其中一些后,我仍然无法解决这个问题。
作为一个学习练习,我制作了一个简单的脚本,从文件中加载URL,使用request
模块查询它们,并在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){
加载数据并以2D数组的形式返回输入文件的行,该数组在该行之后完整且可用。
For
循环遍历它,加载带有request(url, function(error, response, body) {
行的URL,这是非阻塞的(对吗?),因此For循环继续而不等待以前的URL完成加载。
因此,您可以同时加载多个网址,console.log
内的request
次来电将按照收到回复的顺序打印,而不是输入的顺序文件。
在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
打印输出最后都会一起发生,但为什么它们都打印谷歌,更令人费解的是,为什么最后一个人说它是纽约时报的网站,当时的日志线就在前面它(来自同一个request
电话)刚刚打印过谷歌吗?这就像request
调用获取正确的网址一样,但console.log
调用滞后,只是在末尾打印所有内容的结尾值。
有趣的是,如果我颠倒了网址的顺序,输出中的一切看起来都是正确的,我猜是因为网站的响应时间不同:
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 ('');
}
});
});
});
答案 0 :(得分:3)
我理解为什么请求打印输出最后一起发生, 但为什么他们都打印谷歌,更令人费解,为什么呢 最后一个说它是NYT网站,当它在它之前的日志线 (来自同一个请求中)刚刚打印过Google?就像是 请求调用获取正确的URL,但是console.log 电话是滞后的,只是打印出最后的一切 结束价值。
您正确理解for
循环启动了所有request()
次调用,然后在以后的某个时间内以响应返回的顺序完成。
但是,你的日志声明:
console.log('Loading URL: '+url+'\n');
指的是for
循环中的变量,它由for
循环的所有迭代共享。因此,由于for
循环运行完成,然后在某个时间之后所有响应都到达并得到处理,所以当任何响应被处理时,for
循环将完成,因此变量{{当url
循环结束时,它将具有它在其中的任何值,这将是for
循环的最后一次迭代的值。
在ES6中,您可以使用for
而不是let
来定义变量,它将是块作用域,因此每次循环迭代都会有一个唯一的变量var
。 / p>
所以,改变:
url
到
var url = data[r][0];
在ES6之前,避免此问题的常用方法是使用let url = data[r][0];
进行迭代,因为它需要回调函数,因此所有循环代码都在.forEach()
的工作原理的范围内因此每次迭代都有自己的局部变量而不是共享局部变量。
仅供参考,虽然.forEach()
解决了这个问题并且是其设计的问题之一,但我认为如果您只是使用let
进行迭代,那么您的代码可能会更清晰一些使用对当前数组迭代值的单个引用替换对.forEach()
的多个引用。
data[r]
答案 1 :(得分:1)
您的代码很好,而且您的工作原理是正确的(包括响应时间的差异是什么使您在切换顺序时看起来很好),但您的日志记录已成为意外关闭的牺牲品:{{在url
回调的范围内声明和更新1}},并且在两次都记录parse()
的情况下,它会在{{1}之前的循环中更新为其最终值回调开始执行。