Node.js和Redis;等待循环结束

6
我想问这个问题,因为我不确定自己是否理解了Node.js的逻辑。
我有一组需要使用redis的get方法查询的id。在检查特定值之后(假设我正在检查我使用给定“key”获取的对象是否具有空名称),我将它们添加到列表中。这是我的示例代码:
var finalList = [];
var list = [];
redisClient.smembers("student_list", function(err,result){
            list = result; //id's of students
            console.log(result);

            var possibleStudents = [];


            for(var i = 0; i < list.length; i++){


                redisClient.get(list[i], function(err, result){
                    if(err)
                        console.log("Error: "+err);
                    else{
                        tempObject = JSON.parse(result);
                        if(tempObject.name != null){
                            finalList.push(tempObject);
                        }
                    }
                });     
            }

    });
   console.log("Goes here after checking every single object");

但是,由于Node的异步性质,如果不检查列表中的每个id,它就会执行“在这里执行...”。我的需求是在检查每个id(在redis db中映射和检查名称)之后应用其余程序。但我不知道如何做到这一点。也许我可以将回调函数附加到for循环上,并确保在循环完成后开始运行我的其他函数(我知道这是不可能的,但只是为了给个想法)?

3个回答

5

我建议你按照你在问题中提到的方法,并将自定义回调函数附加到你的获取函数:

function getStudentsData(callback) {
    var setList = [];
    var dataList = [];

    redisClient.smembers("student_setList", function(err,result) {
        setList = result; //id's of students

        for(var i = 0; i < setList.length; i++) {
            redisClient.get(setList[i], function(err, result) {
                if(err) {
                    console.log("Error: "+err);
                } else {
                    tempObject = JSON.parse(result);
                    if(tempObject.name != null) {
                        dataList.push(tempObject);
                    }
                }
            });     
        }

        if(dataList.length == setList.length) {
            if(typeof callback == "function") {
                callback(dataList);
            }
            console.log("getStudentsData: done");
        } else {
            console.log("getStudentsData: length mistmach");
        }

    });
}

getStudentsData(function(dataList) {
    console.log("Goes here after checking every single object");
    console.log(dataList.length);
    //More code here
});

这可能是最有效的方法;另外,您可以使用老式的while循环,直到数据准备就绪:

var finalList = [];
var list = [0];

redisClient.smembers("student_list", function(err,result) {
    list = result; //id's of students
    var possibleStudents = [];

    for(var i = 0; i < list.length; i++) {
        redisClient.get(list[i], function(err, result) {
            if(err) {
                console.log("Error: "+err);
            } else {
                tempObject = JSON.parse(result);
                if(tempObject.name != null) {
                    finalList.push(tempObject);
                }
            }
        });     
    }
});


process.nextTick(function() {
    if(finalList.length == list.length) {
        //Done
        console.log("Goes here after checking every single object");
        console.log(dataList.length);
        //More code here
    } else {
        //Not done, keep looping
        process.nextTick(arguments.callee);
    }
});

我们使用process.nextTick而不是实际的while来确保在此期间其他请求不会被阻塞;由于JavaScript的单线程性质,这是首选方式。我将这个内容加入完整性考虑,但前一种方法更有效,并且与node.js更匹配,因此请使用它,除非涉及重大重写。
值得注意的是,这两种情况都依赖于异步回调,这意味着任何代码在外部仍然可能在其他代码完成之前运行。例如,使用我们的第一个片段:
function getStudentsData(callback) {
    //[...]
}

getStudentsData(function(dataList) {
    //[...]
});

console.log("hello world");

最后一个console.log几乎肯定会在我们传递给getStudentsData的回调函数被触发之前运行。解决方法?为此设计,这就是node.js的工作方式。在上面的例子中,很容易解决,我们只需要在传递给getStudentsData的回调函数中调用console.log而不是在外部调用它。其他情况需要更多地偏离传统的过程式编码解决方案,但一旦你理解了它,你会发现事件驱动和非阻塞实际上是一个非常强大的功能。


第一个示例对我好像没有起作用:(。请参见[此处] (https://i.gyazo.com/129b071f39bbd1a1c491638be634b00c.png),redis调用是异步的。我的“结果”的控制台日志将首先显示,因为它与您的示例中完全相同的逻辑。 - NiCk Newman

3
尝试使用 finish 模块。我创建了这个模块来解决这个问题。它比 Async 更易于使用,而且性能更好。以下是一个示例:
var finish = require("finish");
finish(function(async) { 
  // Any asynchronous calls within this function will be captured
  // Just wrap each asynchronous call with function 'async'
  ['file1', 'file2', 'file3'].forEach(function(file) {
    async(function(done) { 
      // Your async function should use 'done' as callback, or call 'done' in its callback
      fs.readFile(file, done); 
    });
  });
}, function(err, results) {
  // fired after all asynchronous calls finish or as soon as an error occurs
  console.log(results[0]);console.log(results[1]);console.log(results[2]);
});

3
最好同时发布代码示例和链接,这样如果链接失效,帖子也不会变得无用。 - Matthew
谢谢。已添加代码示例。 - Chaoran
1
警告:如果您使用forEach的数组为空,则finish目前会出现错误,并显示“TypeError:无法读取未定义的属性'kickoff'”。我花了一些时间才找到这个问题,希望能为其他人节省一些痛苦!请参见Github上的问题 - OJFord
@OllieFord 问题已经解决。感谢您的反馈。 - Chaoran
这真是太好了...哦我的上帝。谢谢!! - NiCk Newman

1

尝试使用Node.js的async模块。该模块具有异步forEach功能。


网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接