我是后端的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);
}
});
});
}
最诚挚的问候,
答案 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");
});
}
我希望你能更好地了解这是多么 。