NodeJS UnhandledPromiseRejection警告


134

因此,我正在测试依赖于事件发射器的组件。为此,我想出了将Promises与Mocha + Chai结合使用的解决方案:

it('should transition with the correct event', (done) => {
  const cFSM = new CharacterFSM({}, emitter, transitions);
  let timeout = null;
  let resolved = false;
  new Promise((resolve, reject) => {
    emitter.once('action', resolve);
    emitter.emit('done', {});
    timeout = setTimeout(() => {
      if (!resolved) {
        reject('Timedout!');
      }
      clearTimeout(timeout);
    }, 100);
  }).then((state) => {
    resolved = true;
    assert(state.action === 'DONE', 'should change state');
    done();
  }).catch((error) => {
    assert.isNotOk(error,'Promise error');
    done();
  });
});

在控制台上,尽管正在调用拒绝函数,但我仍收到“ UnhandledPromiseRejectionWarning”消息,因为它会立即显示消息“ AssertionError:Promise错误”

(节点:25754)UnhandledPromiseRejectionWarning:未处理的承诺拒绝(拒绝ID:2):AssertionError:Promise错误:预期{对象(消息(showDiff,...)}虚假1)应以正确的事件进行转换

然后2秒后

错误:超时超过2000毫秒。确保此测试中正在调用done()回调。

自从执行catch回调以来,这甚至更奇怪(我认为由于某种原因断言失败阻止了其余的执行)

现在,有趣的是,如果我将其注释掉,assert.isNotOk(error...)则测试运行正常,控制台中没有任何警告。就执行捕获而言,它仍然“失败”。
但是,尽管如此,我还是无法理解这些错误。有人可以启发我吗?


我认为您在最后一行还有另外一组右括号和括号。请删除它们,然后重试。
Redu

4
这太酷了,新的未处理的拒绝警告可以发现现实生活中的错误并节省人们的时间。在这里赢了很多。如果没有此警告,您的测试将超时而没有任何解释。
本杰明·格林鲍姆

Answers:


161

此问题是由以下原因引起的:

.catch((error) => {
  assert.isNotOk(error,'Promise error');
  done();
});

如果断言失败,它将引发错误。该错误将done()永远不会被调用,因为代码在此之前出错。这就是导致超时的原因。

“未处理的承诺拒绝”也由断言失败造成的,因为如果一个错误在被抛出catch()的处理程序,并没有后续的catch()处理程序,错误将得到吞噬(说明这篇文章)。该UnhandledPromiseRejectionWarning警告提醒你这个事实。

通常,如果要在Mocha中测试基于承诺的代码,则应依靠Mocha本身已经可以处理承诺的事实。您不应该使用done(),而是从测试中返回一个承诺。摩卡咖啡然后会自己捕获任何错误。

像这样:

it('should transition with the correct event', () => {
  ...
  return new Promise((resolve, reject) => {
    ...
  }).then((state) => {
    assert(state.action === 'DONE', 'should change state');
  })
  .catch((error) => {
    assert.isNotOk(error,'Promise error');
  });
});

7
对于任何好奇的人,茉莉花也是如此。
尼克·拉德福德

@robertklep是否会因为任何错误而不是您期望的错误而被捕获?我认为如果您要断言失败,这种风格将行不通。
TheCrazyProgrammer

1
@TheCrazyProgrammer catch处理程序可能应该作为的第二个参数传递then。但是,我不完全确定OP的意图是什么,因此我将其保持原样。
robertklep

1
并且对于任何对茉莉花感到好奇的人,done.fail('msg')在这种情况下都可以使用。
帕维尔

您能否提供完整的示例说明主代码vs断言应该去哪里,在这里不是很清楚...尤其是如果您从代码中的原始服务/承诺调用中断言一个值。我们代码的实际承诺是否在其他“摩卡承诺”之内?
bjm88

10

与sinon打桩时出现此错误。

解决的方法是在使用存根解决或拒绝promise时使用npm软件包sinon-promised

代替 ...

sinon.stub(Database, 'connect').returns(Promise.reject( Error('oops') ))

用 ...

require('sinon-as-promised');
sinon.stub(Database, 'connect').rejects(Error('oops'));

还有一个resolves方法(请注意末尾的)。

参见http://clarkdave.net/2016/09/node-v6-6-and-asynchronously-handled-promise-rejections


1
从版本2开始,Sinon现在包括针对存根的“ resolves ”和“ rejects”方法。请参阅npmjs.com/package/sinon-as-promised。不过,我仍然+1了答案-我对此一无所知。
安德鲁(Andrew)

9

如果断言不正确,则Mocha中的断言库通过抛出错误来工作。即使在提供给该catch方法的执行程序函数中抛出错误,也会导致承诺被拒绝。

.catch((error) => {
  assert.isNotOk(error,'Promise error');
  done();
});

在上面的代码中,对象的error求值结果为,true因此断言库将引发错误...永远不会发现。由于错误的结果,done永远不会调用该方法。Mocha的done回调函数会接受这些错误,因此您可以简单地在Mocha中结束所有诺言链.then(done,done)。这确保了总是调用完方法,并且将以与Mocha在同步代码中捕获断言的错误时相同的方式报告错误。

it('should transition with the correct event', (done) => {
  const cFSM = new CharacterFSM({}, emitter, transitions);
  let timeout = null;
  let resolved = false;
  new Promise((resolve, reject) => {
    emitter.once('action', resolve);
    emitter.emit('done', {});
    timeout = setTimeout(() => {
      if (!resolved) {
        reject('Timedout!');
      }
      clearTimeout(timeout);
    }, 100);
  }).then(((state) => {
    resolved = true;
    assert(state.action === 'DONE', 'should change state');
  })).then(done,done);
});

我赞扬本文的想法,即在Mocha中测试promise时使用.then(done,done)的想法。


6

对于那些正在UnhandledPromiseRejectionWarning测试环境之外寻找错误/警告的人,可能是因为代码中没有人照顾承诺中的最终错误:

例如,此代码将显示此问题中报告的警告:

new Promise((resolve, reject) => {
  return reject('Error reason!');
});

(node:XXXX) UnhandledPromiseRejectionWarning: Unhandled promise rejection (rejection id: 1): Error: Error reason!

并添加.catch()或处理错误应解决警告/错误

new Promise((resolve, reject) => {
  return reject('Error reason!');
}).catch(() => { /* do whatever you want here */ });

或在then函数中使用第二个参数

new Promise((resolve, reject) => {
  return reject('Error reason!');
}).then(null, () => { /* do whatever you want here */ });

1
当然,但是我认为在现实生活中,我们通常不只是new Promise((resolve, reject) => { return reject('Error reason!'); })在函数中使用,而在函数function test() { return new Promise((resolve, reject) => { return reject('Error reason!'); });}内部我们不需要使用.catch()而是成功处理错误,当调用该函数test().catch(e => console.log(e))或异步/等待版本时,足以使用该错误try { await test() } catch (e) { console.log(e) }
mikep

1

我遇到了这个问题:

(节点:1131004)UnhandledPromiseRejectionWarning:未处理的承诺拒绝(拒绝ID:1):TypeError:res.json不是一个函数(节点:1131004)DeprecationWarning:不处理未处理的承诺拒绝。将来,未处理的承诺拒绝将以非零退出代码终止Node.j的过程。

这是我的错误,我正在替换res对象then(function(res),因此更改res为result,现在可以正常工作了。

错误

module.exports.update = function(req, res){
        return Services.User.update(req.body)
                .then(function(res){//issue was here, res overwrite
                    return res.json(res);
                }, function(error){
                    return res.json({error:error.message});
                }).catch(function () {
                   console.log("Promise Rejected");
              });

更正

module.exports.update = function(req, res){
        return Services.User.update(req.body)
                .then(function(result){//res replaced with result
                    return res.json(result);
                }, function(error){
                    return res.json({error:error.message});
                }).catch(function () {
                   console.log("Promise Rejected");
              });

服务代码:

function update(data){
   var id = new require('mongodb').ObjectID(data._id);
        userData = {
                    name:data.name,
                    email:data.email,
                    phone: data.phone
                };
 return collection.findAndModify(
          {_id:id}, // query
          [['_id','asc']],  // sort order
          {$set: userData}, // replacement
          { "new": true }
          ).then(function(doc) {
                if(!doc)
                    throw new Error('Record not updated.');
                return doc.value;   
          });
    }

module.exports = {
        update:update
}

1

这是我对E7异步/等待的体验

万一您async helperFunction()在测试中打来电话...(ES7的一项特殊规定async我的意思是关键字的)

→确保您将其称为 await helperFunction(whateverParams) (是的,自然,一旦您知道...)

为了使其正常工作(避免“等待是保留字”),您的测试功能必须具有外部异步标记:

it('my test', async () => { ...

4
你不具备调用它作为await helperFunction(...)。一个async函数返回一个promise。您可以像async对待未标记的函数一样处理返回的承诺,而碰巧会返回一个承诺。关键是要兑现承诺。该功能是否有效async无关紧要。await只是兑现承诺的多种方式中的一种
路易斯

1
真正。但是,然后我必须在捕获方面投入资金...否则我的测试会以误报通过,并且所有失败的承诺都将变得毫无道理(根据测试结果)。因此,等待似乎对我来说减少了工作量。–无论如何,忘记了等待的原因是什么导致UnhandledPromiseRejectionWarning了我……因此得到了这个答案。
Frank Nocke

0

我在Chai-Webdriver for Selenium上也有类似的经历。我加了await了断言,并解决了该问题:

使用Cucumberjs的示例:

Then(/I see heading with the text of Tasks/, async function() {
    await chai.expect('h1').dom.to.contain.text('Tasks');
});

-7

卸载webpack后,我解决了这个问题(js问题)。

sudo uninstall webpack
By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.