我来自PHP背景,试图围绕“事件驱动”的Node.js环境。我编写了一个小脚本,从目录中读取文件,并使用节目,季节和剧集编号的标题更新Redis(如果它们晚于当前存储在数据库中的内容)。看来我遇到了一个异步问题我无法完全理解Redis DB的标题为“我的秀”,季节“05”和标题“01”。正在读取的文件包含“My Show S05E02”和“My Show S05E01”。
数据库应该只在季节/剧集晚于当前季节/剧集时更新,但是由于“updateTitle”被非常快速地调用,并且由于某种原因“My Show S05E02”在“我的节目S05E01”之前传递了更新功能始终将两个值都与原始值“我的显示S05E01”进行比较,因此它会用E02更新Redis,然后再用E01更新!
以下是代码:
function processFiles()
{
fs.readdir(WATCH_DIR, function(err, files){
for (var i = 0; i <= files.length; i++)
{
checkFile(files[i]);
}
});
}
function updateTitle(title, season, episode)
{
var cur_season, cur_episode;
redis_client.hget(title, 'Season', function(err, data){
cur_season = data;
redis_client.hget(title, 'Episode', function(err, data){
cur_episode = data;
redis_client.sismember('Titles', title, function(err, data){
console.log('comparing S'+season+'E'+episode+' to current S'+cur_season+'E'+cur_episode);
if ((season == cur_season && episode >= cur_episode) || season > cur_season)
{
redis_client.hset(title, 'Season', season);
redis_client.hset(title, 'Episode', episode);
console.log('setting '+title+' S'+season+'E'+episode);
}
});
});
});
}
function checkFile(file, mtime)
{
var reg = new RegExp("^"+FILE_PREFIX);
var seasoned = new RegExp("S(\\d{2})E(\\d{2})", "i");
var cache = {}
if (reg.test(file))
{
fs.stat(WATCH_DIR + file, function(err, stats){
console.log(file, stats.mtime.toLocaleDateString() +' '+ stats.mtime.toLocaleTimeString() );
fs.readFile(WATCH_DIR + file, 'utf8', function(ferr, data){
if (seasoned.test(data))
{
title = data.replace(/S(\d{2})E(\d{2})(.*?)$/, '')
.replace(/[\._\-]+/, ' ')
.replace(/^\s+/, '')
.replace(/\s+$/, '');
var season = data.match(/S(\d{2})/i);
season = season[1];
var episode = data.match(/E(\d{2})/i);
episode = episode[1];
updateTitle(title, season, episode);
}
});
});
}
}
fs.watch(WATCH_DIR, function(type, file){
if (type == 'change')
{
processFiles();
}
});
任何帮助将不胜感激。我确信这里还有其他错误或最佳做法,也可以随意分享这些错误 - 但我只是在试图找出异步问题时碰到了我的头!
仅供参考 - 这只是一个宠物项目,以便我能记住我正在观看的每一集我喜欢看的节目。
答案 0 :(得分:2)
问题是您在这里没有保证的执行顺序。例如,它可能按此顺序发生:
如果第一个操作取决于第二个操作的结果,则需要确保第一个操作在开始第二个操作之前完成,这通常是使用回调完成的。
考虑一下:
function doSomethingAsync (num) {
console.log('Starting something ' + num);
setTimeout(function () {
console.log('Done doing something ' + num);
}, 10);
}
function runEverything () {
for (var i = 0; i < 3; i++)
doSomethingAsync(i);
}
runEverything();
输出:
Starting something 0
Starting something 1
Starting something 2
Done doing something 0
Done doing something 1
Done doing something 2
但是,如果我们添加一个回调结构并替换runEverything
中的循环来使用这些回调,那么它将等待之前的doSomethingAsync
完成,然后才开始执行下一个回调:
function doSomethingAsync (num, callback) {
console.log('Starting something ' + num);
setTimeout(function () {
console.log('Done doing something ' + num);
callback();
}, 10);
}
function runEverything () {
var i = 0;
var doneCallback = function () {
if (++i < 3)
doSomethingAsync(i, doneCallback);
};
doSomethingAsync(i, doneCallback);
}
输出:
Starting something 0
Done doing something 0
Starting something 1
Done doing something 1
Starting something 2
Done doing something 2
欢迎使用Node.js。
答案 1 :(得分:1)
这可能不是解决问题的最佳解决方案,但它可能是最简单。
你是对的 - 它被设置回E01的原因是由于竞争条件。它正试图将剧集设置为02,然后转到下一个文件,即01,但它还没有赶上并完成设置为02,所以它读取01.这是有问题的一行:
if ((season == cur_season && episode >= cur_episode) || season > cur_season)
如您所见,您正在检查是否episode >= cur_episode
。将其更改为episode > cur_episode
,您应该至少解决在这种特定情况下倒退的剧集。如果当前剧集为01,它可能仍然会遇到问题,它会在03中写入,然后是02,并且在写入03之前02会覆盖03。您可以尝试阅读所有更改的剧集,然后检查它们并确定哪个是最后一个应用程序,而不是重复写入和读取redis。
你真的想要解决竞争条件,它可以解决一般情况并使你成为一个更强大的Node程序员,但是你会遇到各种不同的方法来解决这个问题(延迟,回调,承诺)等等......)远远超出了本次讨论的范围。我建议你read up,continue learning祝你好运!
答案 2 :(得分:1)
未提及的解决方案是使用async库。使用此模块,您可以轻松地序列化所有“checkFile”调用。您将有效地将所有调用添加到队列中,每个调用在执行前等待前一个调用完成。虽然此解决方案可能运行速度稍慢,但您最终不应该遇到任何您遇到的控制流问题。