8

I'm new to Node.Js and JavaScript web development on the backend. I see that callbacks inside callbacks could be a pain and there are modules to avoid that. One of these modules is async, https://github.com/caolan/async

I've read the documentation but it is hard to start and understand how to do it.

For example, I've this function "check_aut_user", How can I convert this code using 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);
            }               
        });     
    });
}

Best Regards,

4

3 回答 3

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会。其背后的原因是很难移动、添加或删除 a 中的步骤waterfall,这是错误的根源。让您无需担心即可添加步骤,auto并且顺序/并行性由异步处理。

这显然使用了更多的垂直空间,但我认为这是为模块化付出的小代价。

于 2014-03-01T01:22:29.813 回答
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]

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

于 2014-02-28T19:30:10.930 回答
0

这是修改为在需要时使用异步的代码。

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");
    });
}

我希望你能看到这要好得多

于 2014-01-07T01:20:07.567 回答