Node.js /asynchronous – 如何避免与asynchronouscallback地狱?

我是在后端新Node.Js和JavaScript的Web开发。 我看到callback里面的callback可能是一个痛苦,有模块可以避免这种情况。 其中一个模块是async, https://github.com/caolan/async

我已经阅读了文档,但很难开始并理解如何去做。

例如,我有这个函数“check_aut_user”,我怎样才能转换这个代码使用asynchronous?

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

最好的祝福,

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

  • 匿名内联函数
  • 缩进。

两者中的任何一个都很好,但是它们一起使得代码变得僵硬和不可维护。 避免callback地狱的解决scheme是通过以下方法来避免这两件事情:

  • 命名你的函数,并把它们从函数调用中拉出来。
  • 提前回来避免意图。

按照这些原则,您的代码可以被重写为:

 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让你添加步骤而不用担心,顺序/并行是由asynchronous处理。

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

有办法打击使用function性编程技术失控嵌套。 我使用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] 

第二个例子更紧凑,但是添加的嵌套越多,它的可读性就越差。 而且,通过分解实现,可以以多种方式重用组件函数,并且可以为各个组件函数编写unit testing,而不仅仅是为了高级function。

这里是修改为在需要时使用asynchronous的代码。

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

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

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

这两种方法都会callback。 这是一个重写:

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

我希望你能看到这样好多