Node.js 承诺会间歇性地失败,即使在处理时也是如此
Posted
技术标签:
【中文标题】Node.js 承诺会间歇性地失败,即使在处理时也是如此【英文标题】:Node.js promise fails intermittently, even when handled 【发布时间】:2020-10-29 06:09:27 【问题描述】:我正在通过创建一个简单的博客应用程序来学习使用 MongoDB。但是,我保存给定帖子的部分代码似乎偶尔会出现承诺问题,但并非总是如此,而代码是否成功似乎只是运气。
我数据库中的每个帖子都使用以下架构存储:
title: String,
author: String,
body: String,
slug: String,
baseSlug: String,
published: type: Boolean, default: false
slug
定义用于访问博客文章的链接,并根据博客文章的标题自动生成。但是,如果文章标题重复,slug
将在末尾添加一个数字以区别于类似文章,而baseSlug
将保持不变。例如:
"My first post"
,并为其分配了"my-first-post"
的baseSlug
。因为没有其他帖子具有相同的baseSlug
,所以slug
也设置为"my-first-post"
。
我创建了另一个名为"My first post"
的帖子,它被分配了"my-first-post"
的baseSlug
。但是,由于另一个帖子具有相同的baseSlug
,因此它被分配了slug
"my-first-post-1"
。
为了创建这种行为,我在 Express 中编写了以下 addpost
路由:
app.post("/addpost", (req, res) =>
let postInfo = req.body;
for (key of Object.keys(postInfo))
if (postInfo[key] == "true") postInfo[key] = true;
let slug = postInfo.title
.toLowerCase()
.split(" ")
.filter(hasNumber) // return /\d/.test(str);
.slice(0, 5)
.join("-");
postInfo.slug = slug;
var postData;
Post.find( baseSlug: postInfo.slug , (error, documents) =>
if (documents.length > 0)
let largestSlugSuffix = 0;
for (let document of documents)
var fullSlug = document.slug.split("-");
var suffix = fullSlug[fullSlug.length - 1];
if (!isNaN(suffix))
if (parseInt(suffix) > largestSlugSuffix)
largestSlugSuffix = suffix;
largestSlugSuffix++;
postInfo.baseSlug = postInfo.slug;
postInfo.slug += "-" + largestSlugSuffix;
else
postInfo.baseSlug = postInfo.slug;
postData = new Post(postInfo);
)
.then(() =>
postData
.save()
.then(result =>
res.redirect("/");
)
.catch(err =>
console.log(err);
res.status(400).send("Unable to save data");
);
)
.catch(err =>
console.log(err);
res.status(400).send("Unable to save data");
);
);
这段代码似乎大部分时间都可以工作,但有时会失败,并输出以下内容:
TypeError: Cannot read property 'save' of undefined
at C:\Users\User\BlogTest\app.js:94:18
at processTicksAndRejections (internal/process/task_queues.js:94:5)
(供参考,我文件中的第 94 行是postData.save()
)
我怀疑这是因为函数主体的执行时间比它应该执行的要长,而且 postData
变量尚未定义。但是,由于.then()
回调函数,postData.save()
不应该在 Promise 完成之前执行。
为什么我的代码会这样?有什么办法可以解决吗?
【问题讨论】:
【参考方案1】:问题是您将 Promise 与回调和闭包混合在一起。这不是它的预期工作方式。
当您链接 Promise 时,您在第一个 Promise 处理程序中返回的任何内容都将作为输入添加到下一个 Promise 处理程序。如果你返回一个 Promise,该 Promise 将在被发送到下一个 thenable 之前首先被解析。
所以你需要从你的 Promise 中返回 Promise,像这样:
app.post("/addpost", (req, res) =>
let postInfo = req.body;
for (key of Object.keys(postInfo))
if (postInfo[key] == "true") postInfo[key] = true;
let slug = postInfo.title
.toLowerCase()
.split(" ")
.filter(hasNumber) // return /\d/.test(str);
.slice(0, 5)
.join("-");
postInfo.slug = slug;
// var postData; <-- Don't do that
Post.find( baseSlug: postInfo.slug )
.then((documents) =>
if (documents.length > 0)
let largestSlugSuffix = 0;
for (let document of documents)
var fullSlug = document.slug.split("-");
var suffix = fullSlug[fullSlug.length - 1];
if (!isNaN(suffix))
if (parseInt(suffix) > largestSlugSuffix)
largestSlugSuffix = suffix;
largestSlugSuffix++;
postInfo.baseSlug = postInfo.slug;
postInfo.slug += "-" + largestSlugSuffix;
else
postInfo.baseSlug = postInfo.slug;
return new Post(postInfo);
// We could actually have called postData.save() in this method,
// but I wanted to return it to exemplify what I'm talking about
)
// It is important to return the promise generated by postData.save().
// This way it will be resolved first, before invoking the next .then method
.then( (postData) => return postData.save(); )
// This method will wait postData.save() to complete
.then( () => res.redirect("/"); )
.catch( (err) =>
console.log(err);
res.status(400).send("Unable to save data");
);
);
使用 async/await 可以大大简化:
app.post("/addpost", async (req, res) =>
try
let postInfo = req.body;
for (key of Object.keys(postInfo))
if (postInfo[key] == "true") postInfo[key] = true;
let slug = postInfo.title
.toLowerCase()
.split(" ")
.filter(hasNumber)
.slice(0, 5)
.join("-");
postInfo.slug = slug;
let documents = await Post.find( baseSlug: postInfo.slug );
if (documents.length > 0)
let largestSlugSuffix = 0;
for (let document of documents)
var fullSlug = document.slug.split("-");
var suffix = fullSlug[fullSlug.length - 1];
if (!isNaN(suffix))
if (parseInt(suffix) > largestSlugSuffix)
largestSlugSuffix = suffix;
largestSlugSuffix++;
postInfo.baseSlug = postInfo.slug;
postInfo.slug += "-" + largestSlugSuffix;
else
postInfo.baseSlug = postInfo.slug;
let postData = new Post(postInfo);
await postData.save();
res.redirect("/");
catch (err)
console.log(err);
res.status(400).send("Unable to save data");
;
);
【讨论】:
感谢您的帮助!我发现 async/await 解决方案既优雅又直观。接受的答案【参考方案2】:你正在混合回调和承诺,虽然它可能会做一些事情,但我不确定它会做什么。您应该选择其中一种,不要尽可能多地混合它们。如果您使用的语言支持异步/等待,我建议选择 Promise,否则选择回调。
例如,您的外部处理程序可能是异步函数
app.post("/addpost", async (req, res) =>
//...
)
您真正的错误是在处理Post.find
时,您在某种程度上使用回调和承诺来处理它,并且可能发生的事情是它的随机性,首先调用回调或承诺解决方案。既然你有一个异步函数,你就应该这样做,而不是两者都这样做:
try
const posts = await Post.find( baseSlug: postInfo.slug );
// stuff you were doing in the callback
const post = new Post(postInfo)
// Now the promise code
await post.save()
// success!
res.redirect("/");
catch (err)
// With an async function you can just catch errors like normal
console.log(err);
res.status(400).send("Unable to save data");
如果你没有使用 webpack 或 typescript 并且不能以 es7 为目标,因此不能使用 async/await,那么我建议只使用回调,不要使用 .then
或 .catch
,这看起来更像:
function error(err)
console.log(err)
res.status(400).send("Unable to save data")
Post.find( baseSlug: postInfo.slug , (err, documents) =>
if (err) return error(err)
// stuff you're doing in the callback now
const post = new Post(postInfo)
post.save((err) =>
if (err) return error(err)
// success!
res.redirect("/");
)
)
【讨论】:
以上是关于Node.js 承诺会间歇性地失败,即使在处理时也是如此的主要内容,如果未能解决你的问题,请参考以下文章
如何在 Node.js expressjs 的异步对象方法中处理未处理的承诺拒绝?