Node.js是世界末日的金字塔(即使是异步的),您能写得更好吗? [英] Node.js, the pyramid of doom (even with async), can you write it better?

查看:41
本文介绍了Node.js是世界末日的金字塔(即使是异步的),您能写得更好吗?的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

我认为自己是一个非常有经验的node.js开发人员.

I consider myself a very experienced node.js developer.

但是我仍然想知道是否有更好的方法来编写以下代码,这样我就不会遇到厄运的金字塔……现在,我对您很轻松,我有一些代码使我的金字塔高达20地板,不开玩笑;那就是使用async.js !!!

Yet I still wonder if there is a better way to write the following code so I don't get the pyramid of doom... Now I went easy on you, I have some code that my pyramid gets as high as 20 floors, no kidding; and that's WITH using async.js !!!

问题实际上是我对预览变量有很多依赖性,因此必须嵌套所有内容.写这本书异步Javascript,用更少的代码构建更多响应的应用程序"的人解释说,他会将这些功能放在根范围内,这肯定会摆脱金字塔,但是现在您将拥有一大堆作用域变量(甚至可能是全局变量,具体取决于您在其声明的作用域),这种污染可能会导致一些非常讨厌的错误(如果在全局空间中设置,这可能会导致与其他脚本发生var冲突(请确保您可以使用自调用函数,更多)yachhh ...或更糟糕的是,因为我们正在处理异步,变量覆盖...).实际上,闭包的美妙之处已荡然无存.

The problem is really that I have many dependencies on previews variables so everything must be nested. The guy that wrote the book "Async Javascript, build more responsive Apps with less code" explains that he would put the functions at the root scope, which sure, would get rid of the pyramid, but now you would have a whole bunch of high scope variables (possibly even global, depending at the scope you declare them at) and this pollution can result in some pretty nasty bugs (this could cause var conflicts with other scripts if set at global space (sure you could use self invoking functions, more yachhh... or even worse, since we are dealing with async, variable overrides...). In fact, the beauty of closure is pretty mush out the door.

他建议做的事情是这样的:

What he recommend is doing something like:

function checkPassword(username, passwordGuess, callback) {
    var passwordHash;
    var queryStr = 'SELECT * FROM user WHERE username = ?';
    db.query(selectUser, username, queryCallback);
    function queryCallback(err, result) {
        if (err) throw err;
        passwordHash = result['password_hash'];
        hash(passwordGuess, hashCallback);
    }

    function hashCallback(passwordGuessHash) {
        callback(passwordHash === passwordGuessHash);
    }
}

再次,不是一种干净的方法恕我直言.

again, not a clean approach IMHO.

因此,如果您看一下我的代码(再次,这只是一个片段,在其他地方我会得到更大的嵌套),您常常会发现我的代码离左边越来越远;这就是使用瀑布和异步forEach之类的东西

So, if you look at my code (again, this is just a snippet, I get much bigger nests in other places) you will often see my code getting further and further apart from the left; and that's with using things like waterfall and async forEach...

这是一个小例子:

ms.async.eachSeries(arrWords, function (key, asyncCallback) {
    pg.connect(pgconn.dbserver('galaxy'), function (err, pgClient, pgCB) {
        statement = "SELECT * FROM localization_strings WHERE local_id = 10 AND string_key = '" + key[0] + "'";
        pgClient.query(statement, function (err, result) {
            if (pgconn.handleError(err, pgCB, pgClient)) return;
            // if key doesn't exist go ahead and insert it
            if (result.rows.length == 0) {
                statement = "SELECT nextval('last_resource_bundle_string_id')";
                pgClient.query(statement, function (err, result) {
                    if (pgconn.handleError(err, pgCB, pgClient)) return;
                    var insertIdOffset = parseInt(result.rows[0].nextval);
                    statement = "INSERT INTO localization_strings (resource_bundle_string_id, string_key, string_revision, string_text,modified_date,local_id, bundle_id) VALUES ";
                    statement += "  (" + insertIdOffset + ",'" + key[0] + "'," + 0 + ",'" + englishDictionary[key[0]] + "'," + 0 + ",10,20)";
                    ms.log(statement);
                    pgClient.query(statement, function (err, result) {
                        if (pgconn.handleError(err, pgCB, pgClient)) return;
                        pgCB();
                        asyncCallback();
                    });
                });
            }
            pgCB();
            asyncCallback();
        });
    });
});

在我的深层脚本中,我计算了25个以上的右括号,CRAZY,同时还记得上一次调用的位置,以便异步继续下一次迭代...

On my deep scripts I counted over 25 closing parenthesis, CRAZY, and all while remembering where to call my last callBack so async continues to next iteration...

是否有解决此问题的方法?还是仅仅是野兽的天真?

Is there a solution to this problem? Or it is just the natrure of the beast?

推荐答案

正如Mithon在回答中所说的那样,promise可以使这段代码更加清晰,并有助于减少重复.假设您创建了两个返回承诺的包装函数,分别对应于您正在执行的两个数据库操作 connectToDb queryDb .然后,您的代码可以编写为:

As Mithon said in his answer, promises can make this code much clearer and help to reduce duplication. Let's say that you create two wrapper functions that return promises, corresponding to the two database operations you're performing, connectToDb and queryDb. Then your code can be written as something like:

ms.async.eachSeries(arrWords, function (key, asyncCallback) {
  var stepState = {};
  connectToDb('galaxy').then(function(connection) {
    // Store the connection objects in stepState
    stepState.pgClient = connection.pgClient;
    stepState.pgCB = connection.pgCB;

    // Send our first query across the connection
    var statement = "SELECT * FROM localization_strings WHERE local_id = 10 AND string_key = '" + key[0] + "'";
    return queryDb(stepState.pgClient, statement);
  }).then(function (result) {
    // If the result is empty, we need to send another 2-query sequence
    if (result.rows.length == 0) {
       var statement = "SELECT nextval('last_resource_bundle_string_id')";
       return queryDb(stepState.pgClient, statement).then(function(result) {
         var insertIdOffset = parseInt(result.rows[0].nextval);
         var statement = "INSERT INTO localization_strings (resource_bundle_string_id, string_key, string_revision, string_text,modified_date,local_id, bundle_id) VALUES ";
         statement += "  (" + insertIdOffset + ",'" + key[0] + "'," + 0 + ",'" + englishDictionary[key[0]] + "'," + 0 + ",10,20)";
         ms.log(statement);
         return queryDb(stepState.pgClient, statement);
       });
     }
  }).then(function (result) {
    // Continue to the next step
    stepState.pgCB();
    asyncCallback();
  }).fail(function (error) {
    // Handle a database error from any operation in this step...
  });
});

它仍然很复杂,但是复杂性更易于管理.向每个步骤"添加新的数据库操作不再需要新的缩进级别.还要注意,所有错误处理都在一个地方完成,而不是每次执行数据库操作时都必须添加 if(pgconn.handleError(...))行.

It's still complex, but the complexity is more manageable. Adding a new database operation to every "step" no longer requires a new level of indentation. Also notice that all error handling is done in one place, rather than having to add an if (pgconn.handleError(...)) line every time you perform a database operation.

更新:根据要求,这是定义两个包装器函数的方法.我假设您正在使用 kriskowal/q 作为诺言库:

Update: As requested, here's how you might go about defining the two wrapper functions. I'll assume that you're using kriskowal/q as your promise library:

function connectToDb(dbName) {
  var deferred = Q.defer();
  pg.connect(pgconn.dbserver(dbName), function (err, pgClient, pgCB) {
    if (err) {
      deferred.reject(err)
    } else {
      deferred.resolve({pgClient: pgClient, pgCB: pgCB})
    }
  });
  return deferred.promise;
}

您可以使用此模式为需要一次性回调的任何函数创建包装器.

You can use this pattern to create a wrapper around any function that takes a single-use callback.

queryDb 更加简单,因为其回调为您提供了单个错误值或单个结果值,这意味着您可以使用q的内置 makeNodeResolver 解决或拒绝延迟的实用程序方法:

The queryDb is even more straightforward because its callback gives you either a single error value or a single result value, which means that you can use q's built-in makeNodeResolver utility method to resolve or reject the deferred:

function queryDb(pgClient, statement) {
  var deferred = Q.defer();
  pgClient.query(statement, deferred.makeNodeResolver());
  return deferred.promise;
}

有关承诺的更多信息,请参阅我的书:异步JavaScript ,由PragProg.

For more information on promises, check out my book: Async JavaScript, published by PragProg.

这篇关于Node.js是世界末日的金字塔(即使是异步的),您能写得更好吗?的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

查看全文
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆