Node.js / Async - 如何避免使用异步回调地狱?

时间:2014-01-07 00:32:57

标签: node.js node-async

我是后端的Node.Js和JavaScript Web开发的新手。我看到回调内部的回调可能很痛苦,并且有模块可以避免这种情况。其中一个模块是异步,https://github.com/caolan/async

我已阅读文档,但很难开始并了解如何操作。

例如,我有这个函数“check_aut_user”,如何使用async转换此代码?

function check_auth_user(username, password, done) {

    var client = new pg.Client("pg://user:pass@127.0.0.1/database");
    client.connect(function(err) {
        // If not get the connection
        if(err) { return console.error('could not connect to postgres', err); }

        // Query the user table
        client.query('select * from "user" where username = $1 and password = $2', [username, password], function(err, result) {
            if(err) { return console.error('error running query', err); }

            if (result.rowCount > 0) {
                var res = result.rows[0];
                console.log(res);

                passport.serializeUser(function(res, done) {
                    //console.log("serializer: " + res);
                    done(null, res);
                });

                passport.deserializeUser(function(user, done) {
                    //console.log("deserializer: " + user['password']);
                    done(null, res);
                }); 

                return done(null, res);
            } else {
                return done(null, false);
            }               
        });     
    });
}

最诚挚的问候,

3 个答案:

答案 0 :(得分:4)

有一些方法可以使用函数式编程技术来防止失控嵌套。我使用curry模块将循环体分解为独立的例程;通常情况下,与嵌套相比,这会带来非常小的性能影响(研究curry为什么)。例如:

var curry = require('curry'),
    async = require('async');

var eachBody = curry(function(context, item, callback) {
  callback(null, context + item);  // first argument is error indicator
});

function exampleLoop(data, callback) {
  // use the curried eachBody instead of writing the function inline
  async.map(data, eachBody(data.length), callback);
}

function print(err, result) {
  console.log(result);
}

exampleLoop([2, 4, 6], print);  // prints [5, 7, 9]
exampleLoop([2, 4, 6, 7], print);  // prints [6, 8, 10, 11]

而不是:

var async = require('async');

function exampleLoop(data) {
  async.map(data, function(item, callback) {
    callback(null, data.length + item);  
  }, function(err, result) {
    console.log(result);
  });
}

exampleLoop([2, 4, 6]);  // prints [5, 7, 9]
exampleLoop([2, 4, 6, 7]);  // prints [6, 8, 10, 11]

第二个例子更紧凑,但你添加的嵌套越多,它的可读性就越低。此外,通过拆分实现,您可以以多种方式重用组件函数,并且可以为单个组件函数编写单元测试,而不仅仅是为了高级功能。

答案 1 :(得分:4)

在我看来,回调地狱实际上是两个问题的混合:

  • 匿名内联函数。
  • 压痕。

少量的一个都很好,但是它们一起使代码变得僵硬且不可维护。避免回调地狱的解决方案是避免这两件事:

  • 命名您的函数并将其从函数调用中删除。
  • 尽早回来以避免意图。

遵循这些原则,您的代码可以重写为:

function check_auth_user(username, password, done) {

    // Make a new client and open the connection.
    function connect(callback) {
        var client = new pg.Client("pg://user:pass@127.0.0.1/database");

        client.connect(function (err) {
            if (err) {
                console.error('could not connect to postgres', err);
                return callback(err);
            }

            callback(null, client);
        });
    }

    // Query the database.
    function query(callback, results) {
        var client = results.client;
        var q = 'select * from "user" where username = $1 and password = $2';
        var params = [username, password];

        client.query(q, params, function (err, result) {
            if (err) {
                console.error('error running query', err.stack || err.message);
                return callback(err);
            }

            callback(null, result);
        });
    }

    // Do stuff with the result of the query.
    function handleQueryResult(callback, results) {
        var result = results.query;

        if (result.rowCount === 0) {
            return callback();
        }

        var row = result.rows[0];

        console.log(row);

        passport.serializeUser(function(res, done) {
            done(null, res);
        });

        passport.deserializeUser(function(user, done) {
            done(null, res);
        }); 

        callback(null, row);
    }

    // Let async handle the order. Allows remixing.
    async.auto({
        connect: [connect],
        query: ['connect', query],
        row: ['query', handleQueryResult]
    }, function (err, results) {
        if (err) {
            return done(err);
        }

        // Callback with the row.
        done(null, results.row);
    });
}

我在这里使用了async.auto,即使async.waterfall会这样做。其背后的原因是,在waterfall中移动,添加或删除步骤很困难,而这些都是错误的来源。 auto允许您添加步骤而不用担心,顺序/并行性由异步处理。

这显然使用了更多的垂直空间,但我认为这对模块化来说是一个很小的代价。

答案 2 :(得分:0)

以下是修改后的代码,以便在需要时使用async。

function check_auth_user(username, password) {
    var client = new pg.Client("pg://user:pass@127.0.0.1/database");
    async.waterfall([
      client.connect,
      function(callback) {
        client.query('select * from "user" where username = $1 and password = $2', [username, password], callback);
      },
      function(result, callback) {
        if(result.rowCount > 0) {
                var res = result.rows[0];

                async.series([
                   function(callback) {
                     passport.serializeUser(res, function(res, done) {
                       callback(null, res);
                     }
                   },
                   function(res, callback){
                     if(res) {
                       passport.deserializeUser(res, function(res, done) {
                          callback(null, res);
                       });
                     } else {
                       callback(new Error('SerializeUser failed'));
                     }
                   }
                ], function(err, res) {
                    if(err) {
                       callback(err);
                    } else {
                       callback(null);
                    }
                });
        }
        else {
           callback(new Error("No result"));
        }
      }
    ], function(err, result) {
       if(err)
          console.err(err);
    });
}

显然,它不会使代码更具可读性。我建议进行其他更改:

  • 查询数据库应该用自己的方法分离(它确实是一种模型方法),从而允许错误检查在它自己的“统治”中发生。
  • 出于同样的原因,护照序列化/反序列化应该在单独的方法中完成。

这两种方法都会进行回调。这是重写:

// Given a client, queries for user
function retrieveUser( client, username, password, cb) {
  client.query('select * from "user" where username = $1 and password = $2', [username, password], function(err, users){
    if(err) {
      cb(err);
    }
    else if(users.rowCount < 0) {
      cb(new Error("No user found for username ="+username));
    }
    else {
      cb(null, result.rows[0]);
  });
}

//Uses passport to serialize/deserializeUser
function passportSerialize(user, cb){
      async.series([
       function(callback) {
         passport.serializeUser(user, function(res, done) {
           callback(null, res);
         }
       },
       function(res, callback){
         if(res) {
           passport.deserializeUser(res, function(res, done) {
              if(res) {
                 callback(null, res);
              } else {
                 callback(new Error('deserializeUser failed'));
              } 
           });
         } else {
           callback(new Error('SerializeUser failed'));
         }
       }
    ], cb);
}

因此,我们的主要方法现在变为:

function check_auth_user(username, password) {
    var client = new pg.Client("pg://user:pass@127.0.0.1/database");
    async.waterfall([
      client.connect,
      function(callback) {
        retrieveUser(client, username, password, callback);
      },
      function(user, callback) {
            passportSerialize(user, callback);
      }
    ], function(err, result) {
       if(err)
          console.err(err);
        else
          console.log("User authenticated, let her in");
    });
}

我希望你能更好地了解这是多么