是否可以编写asynchronous Node.js代码;清洁剂;?

是否可以编写asynchronous Node.js代码;清洁剂;?,node.js,asynchronous,Node.js,Asynchronous,在Node.js中编写代码时,我遇到了很多情况,因为很难实现一些与数据库查询(I/O)混合的复杂逻辑 考虑一个用python编写的示例。我们需要迭代一个值数组,对于查询数据库的每个值,然后根据结果计算平均值 def foo: a = [1, 2, 3, 4, 5] result = 0 for i in a: record = find_from_db(i) # I/O operation if not record: raise Error('No re

在Node.js中编写代码时,我遇到了很多情况,因为很难实现一些与数据库查询(I/O)混合的复杂逻辑

考虑一个用python编写的示例。我们需要迭代一个值数组,对于查询数据库的每个值,然后根据结果计算平均值

def foo:
  a = [1, 2, 3, 4, 5]
  result = 0
  for i in a:
    record = find_from_db(i) # I/O operation
    if not record:
      raise Error('No record exist for %d' % i)
    result += record.value

  return result / len(a)
Node.js中的相同任务

function foo(callback) {
  var a = [1, 2, 3, 4, 5];
  var result = 0;
  var itemProcessed = 0;
  var error;
  function final() {
    if (itemProcessed == a.length) {
      if (error) {
        callback(error);
      } else {
        callback(null, result / a.length);
      }
    }
  }
  a.forEach(function(i) {
    // I/O operation
    findFromDb(function(err, record) {
      itemProcessed++;
      if (err) {
        error = err;
      } else if (!record) {
        error = 'No record exist for ' + i;
      } else {
        result += record.value;
      }
      final();
    });
  });
}
您可以看到,这样的代码更难写/读,而且更容易出错。 我的问题是:

  • 有没有办法让上面的Node.js代码更干净
  • 想象一下更复杂的逻辑。例如,当我们从数据库中获取一条记录时,我们可能需要根据某些条件执行另一个数据库查询。在Node.js中,这将成为一场噩梦。处理此类任务的常见模式是什么
  • 根据您的经验,使用Node.js编写代码时,性能的提高是否值得生产力的损失
  • 是否还有其他更易于使用的异步I/O框架/语言
  • 不要使用匿名(未命名)函数,因为它们会使代码变得难看,并且使调试更加困难,因此请始终命名函数,并在函数范围之外定义它们,而不是内联
  • 这是Node.js的一个真正问题(它被称为回调地狱或末日金字塔,…)您可以通过使用承诺或使用具有许多用于处理不同情况的函数(瀑布、并行、串行、自动等)来解决此问题
  • 性能的提高绝对是一件好事,而且没有那么大的损失(当你开始掌握它的时候),Node.js社区也很棒
  • 检查
    回答您的问题:

  • 在处理异步任务时,有这样的库为常见场景提供了各种解决方案。对于“回调地狱”问题,也有很多方法可以避免这种情况,包括(但不限于)命名函数并将其取出、模块化代码以及使用承诺

  • 目前大致上有一种相当常见的模式:使用计数器和函数索引变量以及要调用的函数数组。同样,
    async
    在这里也有帮助,因为它减少了您可能会经常重复的此类样板文件
    async
    目前没有真正允许跳过单个任务的方法,但是如果您正在编写样板文件,您可以自己轻松地完成这项工作(例如,只需将函数索引变量增加2)

  • 根据我自己的经验,如果您在设计javascript代码时考虑到异步,并使用大量工具,如
    async
    ,您将发现使用node进行开发更容易。在节点中编写异步vs同步通常会更加复杂(尽管与回调/承诺相比,生成器、光纤等的编写要简单得多)

  • 我个人认为,基于这一方面来决定一种语言是不值得的。您必须考虑的不仅仅是语言的设计,例如社区的大小、第三方库的可用性、性能、技术支持选项、代码调试的方便性等。

  • 我工作得越多,我就越喜欢它,也越喜欢它。让我给你一个简单的例子,我有一个服务器初始化

    async.parallel ({
        "job1": loadFromCollection1,
        "job2": loadFromCollection2,
    },
    function (initError, results) {
        if (initError) {
            console.log ("[INIT] Server initialization error occurred: " + JSON.stringify(initError, null, 3));
            return callback (initError);
        }
        // Do more stuff with the results
    });
    
    事实上,可以遵循相同的方法,并且可以将不同的参数传递给对应于不同作业的不同函数;例如,见

    老实说,我更喜欢非阻塞的节点方式。我认为node迫使人们有一个更好的设计,有时您会花时间创建更多的定义,并在数组中对函数和对象进行分组,以便编写更好的代码。我认为原因是,最终您希望利用
    async
    的一些变体,并相应地混合和合并内容。在我看来,如果您还考虑到节点是异步的,那么花一些额外的时间和多花一点时间思考代码是值得的

    除此之外,我认为这是一种习惯。为节点编写代码的人越多,就越能改进并编写更好的异步代码。node的好处在于,它确实迫使人们编写更健壮的代码,因为人们开始更加尊重所有函数中的所有错误代码。例如,人们多久检查一次,比如说
    malloc
    new
    是否成功,并且在发出命令后没有
    NULL
    指针的错误处理程序?不过,编写异步代码会迫使人们尊重事件和事件所包含的错误代码。我想一个明显的原因是,我们尊重自己编写的代码,最终我们必须编写返回错误的代码,以便调用方知道发生了什么


    我真的认为您需要给它更多的时间,并开始更多地使用异步。就这些。

    这里的每个人似乎都在建议,这是一个很棒的图书馆。但要给出另一个建议,您应该看看,这是一个新的内置语言(目前有几个非常好的多边形填充)。它允许您以更结构化的方式编写异步代码。例如,请查看以下代码:

    var items = [ 1, 2, 3, 4 ];
    var processItem = function(item, callback) {
        // do something async ...
    };
    var values = [ ];
    items.forEach(function(item) {
        processItem(item, function(err, value) {
            if (err) {
                // something went wrong
            }
            values.push(value);
            // all of the items have been processed, move on
            if (values.length === items.length) {
                doSomethingWithValues(values, function(err) {
                    if (err) {
                        // something went wrong
                    }
                    // and we're done
                });
            }
        });
    });
    function doSomethingWithValues(values, callback) {
        // do something async ...
    }
    
    使用承诺,它将写下如下内容:

    var items = [ 1, 2, 3, 4 ];
    var processItem = function(item) {
        return new Promise(function(resolve, reject) {
            // do something async ...
        });
    };
    var doSomethingWithValues = function(values) {
        return new Promise(function(resolve, reject) {
            // do something async ...
        });
    };
    // promise.all returns a new promise that will resolve when all of the promises passed to it have resolved
    Promise.all(items.map(processItem))
        .then(doSomethingWithValues)
        .then(function() {
            // and we're done
        })
        .catch(function(err) {
            // something went wrong
        });
    

    第二个版本更干净、更简单,几乎没有触及真正权力的表面。而且,正如我所说,承诺作为一种新的内置语言出现在es6中,因此(最终)您甚至不需要在库中加载,它将是可用的。

    只需编写更简洁的代码:

    // parallel version
    function foo (cb) {
      var items = [ 1, 2, 3, 4, 5 ];
      var pending = items.length;
      var result = 0;
    
      items.forEach(function (item) {
        findFromDb(item, function (err, record) {
          if (err) return cb(err);
          if (!record) return cb(new Error('No record for: ' + item))
          result += record.value / items.length;
          if (-- pending === 0) cb(null, result);
        });
      });
    }
    
    与您发布的针对python的9行sloc相比,它的源代码行数为13行。但是,与您发布的python不同,此代码运行p中的所有作业
    // sequential version
    function foo (cb) {
      var items = [ 1, 2, 3, 4, 5 ];
      var len = items.length;
      var result = 0;
    
      (function next () {
        if (items.length === 0) return cb(null, result);
        var item = items.shift();
    
        findFromDb(item, function (err, record) {
          if (err) return cb(err);
          if (!record) return cb(new Error('No record for: ' + item))
          result += record.value / len;
          next();
        });
      })();
    }
    
    var db = require("some-db-abstraction");
    
    function handleWithdrawal(req,res){  
        try {
            var amount=req.param("amount");
            db.select("* from sessions where session_id=?",req.param("session_id"),function(err,sessiondata) {
                if (err) throw err;
                db.select("* from accounts where user_id=?",sessiondata.user_ID),function(err,accountdata) {
                    if (err) throw err;
                        if (accountdata.balance < amount) throw new Error('insufficient funds');
                        db.execute("withdrawal(?,?),accountdata.ID,req.param("amount"), function(err,data) {
                            if (err) throw err;
                            res.write("withdrawal OK, amount: "+ req.param("amount"));
                            db.select("balance from accounts where account_id=?", accountdata.ID,function(err,balance) {
                                if (err) throw err;
                                res.end("your current balance is "  + balance.amount);
                            });
                        });
                    });
                });
            }
            catch(err) {
                res.end("Withdrawal error: "  + err.message);
        }  
    
    var db = require("some-db-abstraction"), wait=require('wait.for');
    
    function handleWithdrawal(req,res){  
        try {
            var amount=req.param("amount");
            sessiondata = wait.forMethod(db,"select","* from session where session_id=?",req.param("session_id"));
            accountdata= wait.forMethod(db,"select","* from accounts where user_id=?",sessiondata.user_ID);
            if (accountdata.balance < amount) throw new Error('insufficient funds');
            wait.forMethod(db,"execute","withdrawal(?,?)",accountdata.ID,req.param("amount"));
            res.write("withdrawal OK, amount: "+ req.param("amount"));
            balance=wait.forMethod(db,"select","balance from accounts where account_id=?", accountdata.ID);
            res.end("your current balance is "  + balance.amount);
            }
        catch(err) {
            res.end("Withdrawal error: "  + err.message);
    }  
    
    var wait = require('wait.for');
    
    //run in a Fiber
    function process() {
      var a = [1, 2, 3, 4, 5];
      var result = 0;
      a.forEach(function(i) {
        // I/O operation
        var record = wait.for(findFromDb,i); //call & wait for async function findFromDb(i,callback)
        if (!record) throw new Error('No record exist for ' + i);
        result += record.value;
      });
    
      return result/a.length;
    }
    
    function inAFiber(){
       console.log('result is: ',process());
    }
    
    // run the loop in a Fiber (keep node spinning)
    wait.launchFiber(inAFiber);