我希望了解回调函数内部和外部的承诺行为



我对NodeJS相当陌生,正在尝试理解异步,等待和承诺。如果我将 resolve() 置于"con.query"的回调范围之外(仍在新的 Promise 范围内),则 likes 变量是未定义的。仅当 solve() 在回调中时才填充它。为什么会这样?例子:

//WORKS
async function findLikes(q, req, res){
var likes_q = `SELECT * FROM PublicStoryLike WHERE authorId = 
${req.body.token_id}`;
var likes;
var lookup = {};
//const query = util.promisify(con.query).bind(con);
const query = new Promise((resolve, reject) => {
con.query(likes_q, (err, result) => {
likes = JSON.stringify(result);
resolve();    //INSIDE con.query
});
})
//DOESN'T WORK
async function findLikes(q, req, res){
var likes_q = `SELECT * FROM PublicStoryLike WHERE authorId = 
${req.body.token_id}`;
var likes;
var lookup = {};
//const query = util.promisify(con.query).bind(con);
const query = new Promise((resolve, reject) => {
con.query(likes_q, (err, result) => {
likes = JSON.stringify(result);
});
resolve(); //OUTSIDE con.query 
})

让我们忘记承诺。这里的误解是关于回调:您的案例可以说明为:

function works(callback) {
setTimeout(function() {
console.log("inside timeout");
callback("result");
}, 1000);
}
function doesnt(callback) {
setTimeout(function() {
console.log("inside timeout");
}, 1000);
callback();
}
works(function(result) { console.log("called back"); });

在工作版本中,您在调用内部回调时回调外部回调,这意味着在您的情况下,结果来自数据库。

在非工作版本中,您直接回调回调,但计时器(或数据库调用)尚未完成,因此结果尚未出现。


通常不应使用全局变量,尤其是在涉及异步时。您的likes变量会让您(并且已经)让您头疼,只需将其完全删除即可。相反,请使用所需的值解析承诺(或回调)。这样,您可以轻松检测错误:

getResult(function(result) {
callback(result); // works, result is ready
});
callback(result); // does not work, throws an error

在您的情况下,这将是:

async function findLikes(q, req, res){
const query = `SELECT * FROM PublicStoryLike WHERE authorId = ${req.body.token_id}`;

return new Promise((resolve, reject) => {
con.query(query, (err, result) => {
if(err) reject(err) else resolve(result);
});
});
}
findLikes(/*...*/)
.then(likes => {
// Work with likes here!
});

第二个代码块中的问题是您要立即解决承诺。您不是在等待con.query方法完成。

由于您想使用 async/await 并且您似乎正在使用util.promisify因此我建议您执行以下操作:

async function findLikes(q, req, res) {
const likes_q = `SELECT * FROM PublicStoryLike WHERE authorId =  ${
req.body.token_id
}`;
const query = util.promisify(con.query).bind(con);
const likes = await query(likes_q);
// ....
}

您可以看到,您无需手动创建承诺。这就是util.promisify会为你做的。你可以简单地await它。

编辑:通过进一步查看您的代码,我发现findLikes看起来像一条快速路由。通常,我不建议将 Web 请求逻辑与数据访问层耦合。

我建议你把它们分开,findLikes一个独立的异步函数:

async function findLikes(authorId) {
const likes_q = `SELECT * FROM PublicStoryLike WHERE authorId =  ${
authorId
}`;
const query = util.promisify(con.query).bind(con);
return await query(likes_q);
}

稍后在您的路线中:

async function findLikesRoute(q, req, res){
const likes = await findLikes(req.body.token_id);
//... work with the data
}

这将为您带来关注点分离的好处。例如,假设您要更改公开数据的方式,您会怎么做?您现在想通过 GraphQL 公开它,而不是通过此快速路由公开它?

如果将数据访问逻辑分开,那将非常容易。

最新更新