【问题标题】:Node.js For-Loop Inner Asynchronous CallbackNode.js For-Loop 内部异步回调
【发布时间】:2012-05-19 16:05:00
【问题描述】:

我必须遵循评估密码是否在用户密码历史记录中的功能。代码如下:

isPasswordInPwdHistory : function(redisClient, userId, encPassword, cb) {

        var key = 'user:' + userId + ':pwdhistory';
        redisClient.llen(key, function(err, reply) {
            if (err) {
                status.results = [ ];
                xutils.addStatusResultsItem(status.results, err, null, null, null);
                cb(status);
            }
            else {
                var numPwds = reply;

                var match = false;
                var funcError;

                for (i=0; i <= numPwds - 1; i++) {
                    var error;
                    var pwdValue;
                    redisClient.lindex(key, i, function(err, reply) {
                        if (err) {
                            console.log('lindex err = ' + err);
                            error = err;
                        }
                        else {
                            console.log('lindex reply = ' + reply);
                            console.log('lindex encPassword = ' + encPassword);
                            console.log('lindex (encPassword === reply) = ' + (encPassword === reply));
                            pwdValue = reply;
                        }
                    });

                    console.log('for-loop error = ' + error);
                    console.log('for-loop pwdValue = ' + pwdValue);
                    console.log('for-loop (encPassword === pwdValue) = ' + (encPassword === pwdValue));

                    if (error) {
                        funcError = error;
                        break;
                    }
                    else if (encPassword === pwdValue) {
                        console.log('passwords match');
                        match = true;
                        break;
                    }
                }

                console.log('funcError = ' + funcError);
                console.log('match = ' + match);

                if (funcError) {
                    status.results = [ ];
                    xutils.addStatusResultsItem(status.results, err, null, null, null);
                    cb(status);
                }
                else
                    cb(match);
            }
        });
    }

这是控制台输出:

for-loop error = undefined
for-loop pwdValue = undefined
for-loop (encPassword === pwdValue) = false
funcError = undefined
match = false
isPasswordInPwdHistory = false
lindex reply = 5f4f68ed57af9cb064217e7c28124d9b
lindex encPassword = 5f4f68ed57af9cb064217e7c28124d9b
lindex (encPassword === reply) = true

一旦我离开 redisClient.lindex() 调用的范围,我就会丢失这些值。如何在 for 循环中传递这些值以进行评估?

更新

当 redisClient.lindex() 回调发出时,我对代码进行了一些重构,以处理新密码 (encPassword) 与索引 i 处现有密码的匹配。

isPasswordInPwdHistory : function(redisClient, userId, encPassword, cb) {

        var status = new Object();
        var key = 'user:' + userId + ':pwdhistory';

        redisClient.llen(key, function(err, reply) {
            if (err) {
                status.results = [ ];
                xutils.addStatusResultsItem(status.results, err, null, null, null);
                cb(status);
            }
            else {
                var numPwds = reply;
                var loopCt = 0;

                for (i=0; i <= numPwds - 1; i++) {
                    loopCt++;
                    redisClient.lindex(key, i, function(err, reply) {
                        if (err) {
                            status.results = [ ];
                            xutils.addStatusResultsItem(status.results, err, null, null, null);
                            cb(status);
                        }
                        else if (encPassword === reply) {
                            status.results = [ ];
                            xutils.addStatusResultsItem(status.results, null, 0, null, true);
                            cb(status);
                        }
                        else if (loopCt === numPwds && encPassword !== reply) {
                            status.results = [ ];
                            xutils.addStatusResultsItem(status.results, null, 0, null, false);
                            cb(status);
                        }
                    });
                }
            }
        });
    }

不幸的是,即使 encPassword === 回复为真并且我发出 cb(status),调用者仍看到状态 === 未定义。发出 cb(status) 后如何永久退出 for 循环?

【问题讨论】:

    标签: node.js asynchronous for-loop callback


    【解决方案1】:

    您的逻辑中似乎混合了同步和异步思维。您的for 循环将尽可能快地向 Redis 发出请求,而无需等待 Redis 响应(这是异步 IO 的本质)。因此,当您的代码运行 console.log('funcError = ' + funcError);console.log('match = ' + match); 时,Redis 可能甚至还没有响应循环中 i 的第一个值(与您在输出中找到的值相匹配)。

    我可能会研究像async 这样的库来帮助完成这项任务;特别是whilst looks like it might be a good fit。也许是这样的:

    var numPwds = reply;
    ...
    var match = false;
    var i = 0;
    
    async.whilst(
      // keep looping until we have a match or we're done iterating the list
      function () { return match == false && i < numPwds; },
    
      // this is our function to actually check Redis
      function (callback) {
        redisClient.lindex(key, i, function(err, reply) {
          if (err) {
            console.log('lindex err = ' + err);
            error = err;
          }
          else {
            if (encPassword === reply) { match = true; } // this will cause `whilst` to stop.
          }
          i++;
          callback();
        });
      },
    
      function (err) {
        // this is called when our first function that calls
        // return match == false && i < numPwds
        // returns false, which will happen when we're out of list elements to check
        // or we have a match. At this point, match being true or false
        // let us know if we found the password.
      }
    );
    

    最后一点,除非您需要支持在“已用密码”列表中多次显示相同的密码,否则您可以通过使用集合或排序集合来为自己省去很多麻烦。

    【讨论】:

    • 布兰登,感谢您的反馈!我一定会签出异步库。至于使用 Redis 列表数据类型:我只担心存储最后 5 个密码。在将新项目添加到此列表并且它超过 5 个元素后,我将列表重新调整为 5 个元素。老实说,我不需要 Set/Sorted Set 的额外内存开销。但是,您指出这一点非常精明。对你最好的家伙!
    • 我在redisClient.lindex()下添加了回调。
    • 克里斯,编辑(您可能应该对您的问题进行编辑)将不起作用,因为您仍然没有处理找到/未找到的行为(也就是传递给 isPasswordInPwdHistory 的主要回调) Redis IO 回调——您在主执行路径中处理它,在任何 Redis 回调之外。您无法完成这项工作——这与失去作用域无关,而是与代码执行的顺序有关。在 Redis 响应其结果之前,您正在调用 cb(match)。跨度>
    • 另外:集合有用的唯一原因是您获得SISMEMBER,因此您可以将整个代码简化为对该函数的一次调用。但是,如果您需要按顺序排列,我可以理解使用列表!
    • 更新了每个 cmets 的 redisClient.lindex() 回调。
    猜你喜欢
    • 2017-03-20
    • 2011-11-08
    • 1970-01-01
    • 2013-06-05
    • 2013-12-15
    • 2012-06-03
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多