NodeJS UnhandledPromiseRejectionWarning

134

Então, estou testando um componente que depende de um emissor de evento. Para isso, criei uma solução usando o Promises com 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();
  });
});

No console, estou recebendo um 'UnhandledPromiseRejectionWarning', embora a função de rejeição esteja sendo chamada, pois mostra instantaneamente a mensagem 'AssertionError: Promise error'

(nó: 25754) UnhandledPromiseRejectionWarning: rejeição de promessa sem tratamento (ID de rejeição: 2): AssertionError: erro de promessa: esperado {objeto (mensagem, showDiff, ...)} como falso 1) deve fazer a transição com o evento correto

E então, depois de 2 segundos eu recebo

Erro: tempo limite de 2000ms excedido. Verifique se o retorno de chamada done () está sendo chamado neste teste.

O que é ainda mais estranho desde que o retorno de chamada de captura foi executado (acho que, por algum motivo, a falha de declaração impediu o restante da execução)

Agora, o mais engraçado, se eu comentar, assert.isNotOk(error...)o teste será executado sem nenhum aviso no console. Ainda "falha" no sentido em que executa a captura.
Ainda assim, não consigo entender esses erros com promessa. Alguém pode me esclarecer?

Jzop
fonte
Eu acho que você tem um conjunto extra de chave de fechamento e parens na última linha. Exclua-os e tente novamente.
Redu
4
Isso é tão legal que o novo aviso de rejeição sem tratamento encontra bugs na vida real e economiza tempo para as pessoas. Tanta vitória aqui. Sem esse aviso, seus testes teriam atingido o tempo limite sem nenhuma explicação.
Benjamin Gruenbaum

Respostas:

161

O problema é causado por isso:

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

Se a asserção falhar, gerará um erro. Este erro fará com que done()nunca seja chamado, porque o código foi exibido antes dele. Isso é o que causa o tempo limite.

A "rejeição de promessa não tratada" também é causada pela falha na declaração, porque se um erro for gerado em um catch()manipulador e não houver um catch()manipulador subsequente , o erro será engolido (conforme explicado neste artigo ). O UnhandledPromiseRejectionWarningaviso está alertando você para esse fato.

Em geral, se você deseja testar o código baseado em promessas no Mocha, deve confiar no fato de que o próprio Mocha já pode lidar com promessas. Você não deve usar done(), mas, em vez disso, retorne uma promessa do seu teste. O Mocha detectará os próprios erros.

Como isso:

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');
  });
});
robertklep
fonte
7
Para quem está curioso, isso também é verdade para o jasmim.
Nick Radford
@robertklep Não vai pegar vai ser chamado por qualquer erro e não apenas o que você está esperando? Acho que esse estilo não funcionará se você estiver tentando afirmar falhas.
TheCrazyProgrammer
1
@TheCrazyProgrammer o catchmanipulador provavelmente deve ser passado como segundo argumento para then. No entanto, eu não sou inteiramente certo o que a intenção do OP foi, então eu deixei como está.
robertklep
1
E também para qualquer pessoa curiosa sobre jasmim, use done.fail('msg')neste caso.
Paweł
você pode dar um exemplo completo de onde o código principal e as asserções devem ir? A promessa real do nosso código está dentro dessa outra "promessa moca"?
bjm88
10

Eu recebi esse erro ao stubbing com sinon.

A correção é usar o pacote npm sinon como prometido ao resolver ou rejeitar promessas com stubs.

Ao invés de ...

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

Usar ...

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

Há também um método resolve (observe os s no final).

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

danday74
fonte
1
O Sinon agora inclui os métodos "resolve" e "rejeita" para stubs a partir da versão 2. Consulte npmjs.com/package/sinon-as-promised . Eu ainda marquei a resposta com +1, apesar de tudo - não sabia disso.
20918 Andrew
9

As bibliotecas de asserções no Mocha funcionam lançando um erro se a asserção não estiver correta. Lançar um erro resulta em uma promessa rejeitada, mesmo quando lançada na função executora fornecida ao catchmétodo.

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

No código acima, a errorobjeção é avaliada para trueque a biblioteca de asserções gere um erro ... que nunca seja detectado. Como resultado do erro, o donemétodo nunca é chamado. O doneretorno de chamada do Mocha aceita esses erros, então você pode simplesmente encerrar todas as cadeias de promessas no Mocha com .then(done,done). Isso garante que o método concluído seja sempre chamado e o erro seja relatado da mesma maneira que quando o Mocha captura o erro da asserção no código síncrono.

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);
});

Dou crédito a este artigo pela idéia de usar .then (concluído, concluído) ao testar promessas no Mocha.

Matthew Orlando
fonte
6

Para quem procura o erro / aviso UnhandledPromiseRejectionWarning fora de um ambiente de teste, pode ser porque provavelmente ninguém no código está cuidando do eventual erro em uma promessa:

Por exemplo, este código mostrará o aviso relatado nesta pergunta:

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

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

e adicionar .catch()ou manipular o erro deve resolver o aviso / erro

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

Ou usando o segundo parâmetro na thenfunção

new Promise((resolve, reject) => {
  return reject('Error reason!');
}).then(null, () => { /* do whatever you want here */ });
gsalgadotoledo
fonte
1
Claro, mas acho que na vida real geralmente não usamos apenas new Promise((resolve, reject) => { return reject('Error reason!'); })funções, mas funções function test() { return new Promise((resolve, reject) => { return reject('Error reason!'); });}internas que não precisamos usar, .catch()mas para lidar com erros com êxito, basta usar ao chamar essa função test().catch(e => console.log(e))ou versão assíncrona / try { await test() } catch (e) { console.log(e) }
aguardada
1

Eu enfrentei esse problema:

(nó: 1131004) UnhandledPromiseRejectionWarning: rejeição de promessa não tratada (ID de rejeição: 1): TypeError: res.json não é uma função (nó: 1131004) DeprecationWarning: rejeições de promessa não tratadas estão obsoletas. No futuro, as rejeições de promessa que não forem tratadas encerrarão o processo do Node.j com um código de saída diferente de zero.

Foi um erro meu, eu estava substituindo um resobjeto then(function(res), então mudeires para resultar e agora está funcionando.

Errado

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");
              });

Correção

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");
              });

Código de Serviço:

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
}
Muhammad Shahzad
fonte
1

Aqui está a minha experiência com o E7 assíncrono / aguardar :

Caso você tenha async helperFunction()recebido uma chamada do seu teste ... (uma explicação com a asyncpalavra-chave ES7 , quero dizer)

→ certifique-se de chamar assim await helperFunction(whateverParams) (bem, sim, naturalmente, depois que você souber ...)

E para que isso funcione (para evitar 'aguardar é uma palavra reservada'), sua função de teste deve ter um marcador assíncrono externo:

it('my test', async () => { ...
Frank Nocke
fonte
4
Você não precisa chamá-lo como await helperFunction(...). Uma asyncfunção retorna uma promessa. Você poderia apenas lidar com a promessa retornada, como faria em uma função não marcada asyncque retornasse uma promessa. O ponto é lidar com a promessa, ponto final. Se a função é asyncou não, não importa. awaité apenas uma entre várias maneiras de lidar com a promessa.
Louis
1
Verdade. Mas então eu tenho que investir linhas na captura ... ou meus testes passam como falsos positivos, e quaisquer promessas não cumpridas não serão atendidas (em termos de resultados do testrunner). Então aguardar parece menos linhas e esforço para mim. - De qualquer forma, esquecer foi o que causou isso UnhandledPromiseRejectionWarningpara mim ... então essa resposta.
precisa
0

Eu tive uma experiência semelhante com o Chai-Webdriver para Selenium. Eu adicionei awaità afirmação e ela corrigiu o problema:

Exemplo usando Cucumberjs:

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

Resolvi esse problema depois de desinstalar o webpack (problema do react js).

sudo uninstall webpack
Mr Fun
fonte