即使已经捕获,Node.js仍会出现UnhandledPromiseRejectionWarning警告

7

我正在使用Node 7.2.1版本的新async/await特性。同时,我像这样使用了基于原生ES6 Promises的mongoose -

const mongoose = require('mongoose');
mongoose.Promise = global.Promise;

我的代码流程如下 -

async function getFollowers(){
    try {
        const followers = await User.getFollowersFromMongo(req.params.userId);
        res.send(followers);
    } catch (err) {
        winston.error('Printing Error = ', err);
        res.status(400).send({success: false, error: err});
    }
}

UserSchema.statics.getFollowersFromMongo = async(userId) => {
    try {
        let aggregateQuery = []; //some syntactical error in mongo query to produce exception

        const followers = await User.aggregate(aggregateQuery);
        return followers.map(follower => follower.followerData);
    }
    catch (err) {
        return Promise.reject(err);
    }
};

这段代码完全没有问题。 问题出现在产生错误时。因此,我特意修改了我的mongoose查询,以便MongoDB会引发错误。

现在,正如预期的那样,MongoDB引发了一个错误,我的代码完美地捕获并返回给客户端一个400错误代码。

问题是,即使错误(有意)被我捕获,Node.js仍然会给我这个警告-

error:  Printing Error = MongoError: path option to $unwind stage should be prefixed with a '$': followerData
at Function.MongoError.create (/home/node_modules/mongodb-core/lib/error.js:31:11)
at /home/node_modules/mongodb-core/lib/connection/pool.js:483:72
at authenticateStragglers (/home/node_modules/mongodb-core/lib/connection/pool.js:429:16)
at Connection.messageHandler (/home/node_modules/mongodb-core/lib/connection/pool.js:463:5)
at Socket.<anonymous> (/home/node_modules/mongodb-core/lib/connection/connection.js:317:22)
at emitOne (events.js:96:13)
at Socket.emit (events.js:188:7)
at readableAddChunk (_stream_readable.js:176:18)
at Socket.Readable.push (_stream_readable.js:134:10)
at TCP.onread (net.js:551:20)

GET /user/385/followers 400 39.868 ms - 263

(node:10158) UnhandledPromiseRejectionWarning: Unhandled promise rejection (rejection id: 1): MongoError: path option to $unwind stage should be prefixed with a '$': followerData
(node:10158) DeprecationWarning: Unhandled promise rejections are deprecated. In the future, promise rejections that are not handled will terminate the Node.js process with a non-zero exit code.

可以看到,我的请求返回了400状态码,初始方法的catch块也打印了错误日志,但是Node.js仍然提示错误消息未处理。

为什么即使相同的错误被捕获后,它还会说未处理呢?

更新 - 感谢@dvlsg和@Bergi,问题在4.7.5版本中已经修复。


我的猜测是因为你使用了try/catch来捕获它,而不是.catch()。 - Kevin B
1
@KevinB 但是这不是异步/等待处理错误的方式吗? - Jyotman Singh
请问什么是未处理的 Promise 拒绝(Unhandled Promise Rejection)? - Kevin B
不使用.catch()似乎不是问题所在。这个是另一个使用相同方法的示例,它不会产生“未处理”的堆栈跟踪。 - Jyotman Singh
这个例子没有未处理的错误是因为caller1将会从caller2catchPromise.reject(err) - dvlsg
1个回答

2
“mongoose aggregate” 与 “async/await” 的交互似乎存在某些奇怪的问题。在我看来,这似乎是一个错误。如果是这样的话,应该向 mongoose 报告。幸运的是,有一个简单的解决方法:
const followers = await User.aggregate(aggregateQuery).exec();

添加显式的.exec()使我能够如预期地捕获聚合管道错误。
我认为在这里导致混淆的根本问题是有一个额外的Promise在周围漂浮,它被拒绝了但没有被处理。因为从技术上讲,你确实正确地处理了预期的拒绝。否则,你就不会看到“Printing error = ...”被记录。

这是我认为正在发生的事情--

  • 你使用 await User.aggregate()
  • Aggregate#then() 通过与 thenables(我想)一起使用 await 被调用
  • Aggregate#exec() 在内部被 Aggregate#then() 调用
  • Aggregate#exec() 内部 创建 了一个新的 Promise,并且 将被拒绝
    • 我认为,这是未处理的 Promise
  • 由于从 Aggregate#then()Aggregate#exec() 提供了回调函数,因此 Aggregate#exec() 中的 Error 将被 提供给回调函数
  • Aggregate#then() 的回调函数中,一个新 创建的 Promise 被拒绝
    • 我认为这个 Promise 被按预期处理,因为它是从 Aggregate#then() 返回的。
我认为可以通过在 mongoose 的 Aggregate 定义中注释掉 this line 来确认我的怀疑。这将防止未处理的拒绝处理程序被触发。顺便说一下,我并不建议这样做。这只是额外的证据,而不是解决方案,因为现在我只是有一个未被拒绝的 Promise 在空转。
这里是一个相对简单的方法来复现未捕获的拒绝(uncaught rejection),需要在带有node --harmony-async-await命令的环境下运行(已在 node v7.2.1 上测试)。
const mongoose = require('mongoose');
mongoose.Promise = global.Promise;
mongoose.connect('mongodb://localhost/temp');

const userSchema = new mongoose.Schema({
  name: 'string'
});

const User = mongoose.model('User', userSchema);

async function run() {
  try {
    await User.aggregate([]);
  }
  catch (err) {
    console.log('caught expected error:', err);
  }
}

run();

2
如果是这种情况的话,一定应该将其报告给 mongoose 作为一个 bug。 - Bergi
1
谢谢提供源代码链接,正如我所怀疑的那样。修复方法应该是将then方法更改为简单的function(onfulfill, onreject) { return this.exec().then(onfulfill, onreject); },避免使用Promise构造函数反模式,在exec回调中不必要地创建第二个promise。你想开一个GitHub问题吗? - Bergi
当然,我可以做到。 - dvlsg
2
问题可在此处查看:[https://github.com/Automattic/mongoose/issues/4824]。 - dvlsg
是的,在查询末尾应用.exec()不会产生错误。我会关注 GitHub 上的问题,如果您的推理被证明是正确的,那么我将很乐意接受这个答案。 - Jyotman Singh

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接