【问题标题】:Node throws UnhandledPromiseRejectionWarning for Mongoose requests using Promise.all with .catch() statement节点使用带有 .catch() 语句的 Promise.all 为 Mongoose 请求抛出 UnhandledPromiseRejectionWarning
【发布时间】:2023-03-31 00:02:02
【问题描述】:

我是 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/…

标签: javascript node.js promise


【解决方案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 的实际使用还可以,但是:

  1. 人们希望findById 不会解决如果未找到项目,则使用虚假值解决承诺,因此整个第一个then 处理程序似乎没有必要。
  2. 大概save 会返回一个承诺。如果是这样,您不是在处理拒绝或等待解决这些问题。
  3. 我会使用解构来避免arr[0]arr[1],因为很容易忘记顺序。
  4. 没有理由将带有push 调用的then 处理程序与进行保存的then 处理程序分开。
  5. addUserToLeague 应该返回承诺链的结果,以便调用它的代码 A) 知道它何时完成,并且 B) 知道它何时失败。
  6. 不应在addUserToLeague 中处理错误;而是在其调用者中处理它们。
  7. 还有数据为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。解构赋值可以做所有可以做的事情,包括解构对象和数组,为解构元素提供默认值等。
猜你喜欢
  • 2017-11-18
  • 2020-03-24
  • 1970-01-01
  • 2021-06-19
  • 2021-08-27
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2020-07-15
相关资源
最近更新 更多