【问题标题】:Control flow issue with node/redis and callbacks?节点/redis 和回调的控制流问题?
【发布时间】:2011-12-19 19:02:07
【问题描述】:

请问我可以就 node 和 redis 的控制流问题寻求一些建议吗? (也就是试图习惯 JavaScript 的 Python 编码器)

我不明白为什么 client.smembersclient.get(Redis 查找)需要回调而不是简单的语句 - 这让生活变得非常复杂。

基本上我想查询一个集合,然后当我有集合的结果时,我需要对每个结果执行一次获取。 获得所有数据后,我需要将其广播回客户端。

目前我在两个回调中执行此操作,使用一个全局对象,这看起来很乱。我什至不确定它是否安全(代码会等待一个client.get 完成,然后再开始另一个?)。

当前代码如下所示:

var all_users = [];
// Get all the users for this page.
client.smembers("page:" + current_page_id, function (err, user_ids ) {
  // Now get the name of each of those users.
  for (var i = 0; i < user_ids.length; i++) {
     client.get('user:' + user_ids[i] + ':name', function(err, name) {
       var myobj = {};
       myobj[user_ids[i]] = name;
       all_users.push(myobj);  
       // Broadcast when we have got to the end of the loop, 
       // so all users have been added to the list - 
       // is this the best way? It seems messy.  
       if (i === (user_ids.length - 1)) {
           socket.broadcast('all_users', all_users); 
       }
     });       
   }
 });

但这似乎很混乱。这真的是最好的方法吗?在调用 socket.broadcast 之前,我如何确定所有查找都已执行?

挠头提前感谢您的任何建议。

【问题讨论】:

    标签: javascript node.js redis


    【解决方案1】:

    我不明白为什么 client.smembersclient.get(Redis 查找)需要回调而不是简单的语句 - 这让生活变得非常复杂。

    这就是节点。 (我很确定这个话题在这里讨论的次数足够多,看看其他问题,肯定有)

    在调用 socket.broadcast 之前,我如何确定所有查找都已执行?

    这就是回调函数中的err。这有点像 Node 的标准——回调中的第一个参数是错误对象(null,如果一切正常)。所以只需使用这样的东西来确保没有发生错误:

    if (err) {
      ...    // handle errors.
      return // or not, it depends.
    }
    
    ... // process results
    

    但这看起来很乱。

    你会习惯的。实际上,当代码格式正确且项目结构巧妙时,我发现它很好。

    其他方式有:

    • 使用库控制异步代码流(Async.jsStep.js 等)
    • 如果您认为意大利面条式的代码是一团糟,请定义一些函数来处理结果并将它们作为参数而不是匿名参数传递。

    【讨论】:

    • 完全同意提问者的观察,我也在为这些概念而苦苦挣扎。 That's what Node is 好像有点势利,对不起。
    • @SSHThis 就在 Node 网站的 main page 上,它明确表示是这样的——异步 I/O。我只能推测使您选择依赖于您不(也不愿意)理解的概念的技术的原因。尝试得到它,你会喜欢它,如果不是 - 选择不同的东西。
    【解决方案2】:

    如果你完全不喜欢写回调风格的东西,你可能想试试streamlinejs

    var all_users = [];
    // Get all the users for this page.
    var user_ids = client.smembers("page:" + current_page_id, _);
    // Now get the name of each of those users.
    for (var i = 0; i < user_ids.length; i++) {
      var name = client.get('user:' + user_ids[i] + ':name', _);
      var myobj = {};
      myobj[user_ids[i]] = name;
      all_users.push(myobj);  
    }
    socket.broadcast('all_users', all_users);
    

    请注意,此变体的一个缺点是一次只能获取一个用户名。此外,您仍然应该知道这段代码的真正作用。

    【讨论】:

    • 很好的答案,谢谢。不过我想知道:编写回调样式的正确方法是什么?只是这样我在做简单的方法之前就知道正确的方法:)
    【解决方案3】:

    Async 是一个很棒的库,你应该看看。为什么 ?干净的代码/流程/易于跟踪..等

    另外,请记住,您的所有异步函数都将在您的 for 循环之后处理。在您的示例中,它可能会导致错误的“i”值。使用闭包:

     for (var i = 0; i < user_ids.length; i++) { (function(i) {
     client.get('user:' + user_ids[i] + ':name', function(err, name) {
       var myobj = {};
       myobj[user_ids[i]] = name;
       all_users.push(myobj);  
       // Broadcast when we have got to the end of the loop, 
       // so all users have been added to the list - 
       // is this the best way? It seems messy.  
       if (i === (user_ids.length - 1)) {
           socket.broadcast('all_users', all_users); 
       }
     });       
    })(i)}
    

    要知道何时完成,您应该使用递归模式,例如 async(我认为)。自己动手就简单多了。

    async.series({
      getMembers: function(callback) {
         client.smembers("page:" + current_page_id, callback);
      }
    }, function(err, results) {
       var all_users = [];
       async.forEachSeries(results.getMembers, function(item, cb) {
        all_users.push(item);
        cb();
       }, function(err) {
          socket.broadcast('all_users', all_users); 
       });
    });
    

    此代码可能无效,但您应该能够弄清楚如何操作。

    Step 库也不错(我认为只有 30~ 行代码)

    【讨论】:

    • 对于循环,他也可以只做 .forEach
    【解决方案4】:

    我不明白为什么 client.smembers 和 client.get(Redis 查找) 需要是回调而不是简单的声明 - 它使 生活很复杂。

    是的,所以每个人都同意回调地狱不是 bueno。在撰写本文时,回调是 Node.js 的一个垂死的特性。不幸的是,Redis 库不支持返回 Promises。

    但是您可以像这样要求一个模块:

    const util = require("util");

    这是一个包含在 Node 运行时中的标准库,并且有一堆我们可以使用的实用函数,其中一个是“promisify”: https://nodejs.org/api/util.html#util_util_promisify_original

    当然,当您七年前提出这个问题时,util.promisify(original) 并不存在,因为它是在 -v 8.0.0 版本中添加的strong>,所以我们现在可以用更新的答案更新这个问题。

    所以 promisify 是一个函数,我们可以向它传递一个类似 client.get() 的函数,它会返回一个新函数,该函数采用令人讨厌的回调行为,而是将其包装得漂亮而整洁返回一个承诺。

    所以 promisify 将任何接受回调的函数作为最后一个参数,并使其返回一个 Promise,这听起来就像您七年前想要的确切行为,而我们今天可以提供。

    const util = require("util");
    client.get = util.promisify(client.get);
    

    所以我们将对.get() 函数的引用传递给util.promisify()

    这需要你的函数,将它包装起来,所以它不是实现回调,而是返回一个 Promise。所以util.promisify() 返回一个已承诺的新函数。

    因此,您可以采用该新功能并覆盖client.get() 上的现有功能。 如今,您不必为 Redis 查找使用回调。所以现在你可以像这样使用async/await 语法:

    const cachedMembers = await client.get('user:' + user_ids[i]);

    所以我们等待这个问题得到解决,无论它解决了什么都将分配给cachedMembers

    通过使用 ES6 数组辅助方法而不是 for 循环,可以进一步清理代码以进行更多更新。我希望这个答案对当前用户有用,否则 OP 已经过时了。

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 2015-11-27
      • 2011-12-18
      • 2016-05-15
      • 2023-03-23
      • 1970-01-01
      • 2019-09-07
      • 1970-01-01
      • 1970-01-01
      相关资源
      最近更新 更多