节点使用带有 .catch() 语句的 Promise.all 为 Mongoose 请求抛出 UnhandledPromiseRejectionWarning
Posted
技术标签:
【中文标题】节点使用带有 .catch() 语句的 Promise.all 为 Mongoose 请求抛出 UnhandledPromiseRejectionWarning【英文标题】:Node throws UnhandledPromiseRejectionWarning for Mongoose requests using Promise.all with .catch() statement 【发布时间】:2019-07-20 05:15:05 【问题描述】:我是 Node/Mongoose 的新手,我正在尝试在脚本中正确处理错误以将玩家添加到联赛中。在下面的代码中,.catch() 语句正确捕获了显式抛出的和非 Promise 相关的错误,但拒绝的 Promise 则没有。
例如,尝试传递无效的用户 ID 会抛出 User not found
。
但如果我通过断开数据库连接来测试 Promise 拒绝,我会得到以下信息:
(node:6252) UnhandledPromiseRejectionWarning: Unhandled promise rejection (rejection id: 1): MongoNetworkError: failed to connect to server [localhost:27017] on first connect [MongoNetworkError: connect ECONNREFUSED 127.0.0.1:27017]
我是否以某种方式错误地使用了 Promise.all() 和 .catch()?
为了清楚起见,我试图找出错误没有得到处理的原因,而不是抛出错误的原因。
我的脚本:
const
mongoose = require('mongoose'),
User = require('./models/users'),
League = require('./models/leagues'),
dbUrl = process.env.DBURL || 'mongodb://localhost/predictor';
mongoose.connect(dbUrl, useNewUrlParser: true );
const addUserToLeague = (userId, leagueId) =>
let foundUser = User.findById(userId);
let foundLeague = League.findById(leagueId);
return Promise.all([foundUser, foundLeague])
.then(arr =>
if(!arr[0])
throw 'User not found';
else if(!arr[1])
throw 'League not found';
return arr;
)
.then(arr =>
arr[0].leagueMemberships.push(arr[1]);
arr[1].users.push(arr[0]);
return arr;
)
.then(updatedArr =>
updatedArr[0].save();
updatedArr[1].save();
return updatedArr;
)
.then(updatedArr => console.log(`User $updatedArr[0]._id added to league $updatedArr[1]._id`) )
.catch(err => console.log('Error:', err) );
;
addUserToLeague(process.argv[2], process.argv[3]); // Needs 2 args: User ID and League ID
【问题讨论】:
鉴于错误消息是MongoNetworkError: failed to connect to server
,听起来mongoose.connect(…)
返回了一个被拒绝的承诺。
mongoose.connect()
不是示例代码中承诺链的一部分。没有适用于它的 catch 块,因此当连接失败时,您会收到此错误。
^^ Promise.all
的实际使用是可以的,尽管人们希望findById
不会解决如果项目不是,则具有虚假值的承诺找到了,所以整个第一个 then
处理程序似乎没有必要。第二个then
处理程序中的操作最好直接在findById
承诺上处理。另外:save
是否返回承诺?如果是这样,你就没有处理它的拒绝。
这里有一个很好的解释:github.com/petkaantonov/bluebird/issues/…
【参考方案1】:
正如 Bergi 指出的那样,错误似乎来自 connect
,而您根本没有处理 returns a promise — 包括不等待它完成。所以至少,你需要处理:
const connectionPromise = mongoose.connect(dbUrl, useNewUrlParser: true )
.catch(error =>
// Handle connection error
);
然后在addUserToLeague
:
const addUserToLeague = (userId, leagueId) =>
return connectionPromise.then(connection =>
// ...logic here
);
;
...但是,我怀疑您是否应该在这样加载模块时进行连接,而不是将连接传递给addUserToLeague
。
除此之外,Promise.all
的实际使用还可以,但是:
-
人们希望
findById
不会解决如果未找到项目,则使用虚假值解决承诺,因此整个第一个then
处理程序似乎没有必要。
大概save
会返回一个承诺。如果是这样,您不是在处理拒绝或等待解决这些问题。
我会使用解构来避免arr[0]
和arr[1]
,因为很容易忘记顺序。
没有理由将带有push
调用的then
处理程序与进行保存的then
处理程序分开。
addUserToLeague
应该返回承诺链的结果,以便调用它的代码 A) 知道它何时完成,并且 B) 知道它何时失败。
不应在addUserToLeague
中处理错误;而是在其调用者中处理它们。
还有数据为denormalized 的问题:您将会员信息同时存储在用户对象和联赛对象中。也许这在文档数据库中是相对正常的(我不知道);在 RDBMS 中,您会将信息存储在一个单个位置。原因从addUserToLeague
中的代码就很清楚了:如果保存用户成功但保存联盟失败了怎么办?然后用户对象说它是一个联盟的成员,而联盟对象没有说它是一个成员。还有一个问题是,由于它存储在两个地方,即使没有出现任何问题,在短时间内,其中一个(用户或联盟)将被保存,但另一个不会。两者都是完整性问题。如果您可以将其规范化以将这些信息存储在一个地方,那就太好了。如果不能,则需要更新代码,以便保存其中一个,等待其成功,保存另一个,如果失败则尝试撤消对第一个的更改。
类似这样的事情(我不想在这里解决规范化问题,这是一个大局):
const
mongoose = require('mongoose'),
User = require('./models/users'),
League = require('./models/leagues'),
dbUrl = process.env.DBURL || 'mongodb://localhost/predictor';
const addUserToLeague = (connection, userId, leagueId) =>
return Promise.all([
User.findById(userId),
League.findById(leagueId)
])
.then(([user, league]) =>
user.leagueMemberships.push(league);
league.users.push(user);
return Promise.all([user.save(), league.save()]);
)
.then((([user, league]) =>
console.log(`User $user._id added to league $league._id`);
);
;
mongoose.connect(dbUrl, useNewUrlParser: true )
.then(connection => addUserToLeague(connection, process.argv[2], process.argv[3]) // Needs 2 args: User ID and League ID
.catch(error =>
// Handle/report error
);
如果您使用的是任何最新版本的 Node,您可以使用 async
函数:
const
mongoose = require('mongoose'),
User = require('./models/users'),
League = require('./models/leagues'),
dbUrl = process.env.DBURL || 'mongodb://localhost/predictor';
const addUserToLeague = async (connection, userId, leagueId) =>
let [user, league] = await Promise.all([
User.findById(userId),
League.findById(leagueId)
]);
user.leagueMemberships.push(league);
league.users.push(user);
[user, league] = await Promise.all([user.save(), league.save()]);
console.log(`User $user._id added to league $league._id`);
;
mongoose.connect(dbUrl, useNewUrlParser: true )
.then(connection => addUserToLeague(connection, process.argv[2], process.argv[3]) // Needs 2 args: User ID and League ID
.catch(error =>
// Handle/report error
);
【讨论】:
非常感谢您的全面回答,一切都说得通。只是一个跟进 - 我以前没有见过以这种方式传递的参数:.then(([user, league]) => ...
我以前没有见过这种格式。这是将 2 个参数作为数组传递给函数的常规 ES5 方式,还是某种 ES6 格式?我用过解构,但以前没见过这种方式。
@abr - 这是参数解构(ES2015+)。这就像解构赋值,但带有参数。所以function x([a, b]) console.log(`a = $a, b = $b`); const a = [1, 2]; x(a);
输出a = 1, b = 2
。解构赋值可以做所有可以做的事情,包括解构对象和数组,为解构元素提供默认值等。以上是关于节点使用带有 .catch() 语句的 Promise.all 为 Mongoose 请求抛出 UnhandledPromiseRejectionWarning的主要内容,如果未能解决你的问题,请参考以下文章
对Promise中的resolve,reject,catch的理解