节点使用带有 .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的主要内容,如果未能解决你的问题,请参考以下文章

带有while语句范围catch的if语句中的C ++错误

对Promise中的resolve,reject,catch的理解

Java----finally

C#中如何处理异常?怎么使用try-catch语句?

在aardio的函数里try...catch语句中使用return。

Java 多重catch语句的具体使用介绍