NodeJS UnhandledPromiseRejectionWarning


134

Quindi, sto testando un componente che si basa su un emettitore di eventi. Per fare ciò ho trovato una soluzione usando Promises with 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();
  });
});

Sulla console ricevo un "UnhandledPromiseRejectionWarning" anche se viene chiamata la funzione di rifiuto poiché mostra immediatamente il messaggio "AssertionError: Promise error"

(nodo: 25754) UnhandledPromiseRejectionWarning: rifiuto della promessa non gestita (ID rifiuto: 2): errore di asserzione: errore di promessa: previsto {oggetto (messaggio, showDiff, ...)} errato 1) dovrebbe passare con l'evento corretto

E poi, dopo 2 secondi, ottengo

Errore: superamento del timeout di 2000 ms. Assicurarsi che il callback done () venga chiamato in questo test.

Il che è ancora più strano da quando è stato eseguito il callback catch (penso che per qualche motivo il fallimento dell'asserzione abbia impedito il resto dell'esecuzione)

Ora la cosa divertente, se commento assert.isNotOk(error...)il test, va bene senza alcun avvertimento nella console. Resta "fallito", nel senso che esegue la cattura.
Tuttavia, non riesco a capire questi errori con la promessa. Qualcuno può illuminarmi?


Penso che tu abbia un set extra di parentesi graffe e parentesi all'ultima riga. Eliminali e riprova.
Redu,

4
È così bello, il nuovo avviso di rifiuto non gestito trova bug nella vita reale e fa risparmiare tempo alle persone. Tanta vittoria qui. Senza questo avviso i test sarebbero scaduti senza alcuna spiegazione.
Benjamin Gruenbaum,

Risposte:


161

Il problema è causato da questo:

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

Se l'asserzione fallisce, genererà un errore. Questo errore non causerà done()mai di essere chiamato, perché il codice è stato errato prima di esso. Questo è ciò che provoca il timeout.

Il "rifiuto di promessa non gestita" è anche causato dall'asserzione fallita, perché se viene generato un errore in un catch()gestore e non esiste un catch()gestore successivo , l'errore verrà ingoiato (come spiegato in questo articolo ). L' UnhandledPromiseRejectionWarningavvertimento ti avverte di questo fatto.

In generale, se si desidera testare il codice basato sulle promesse in Mocha, è necessario fare affidamento sul fatto che Mocha stessa può già gestire le promesse. Non dovresti usare done(), ma invece, restituisci una promessa dal tuo test. Mocha rileverà quindi eventuali errori.

Come questo:

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
Per chiunque sia curioso, questo vale anche per il gelsomino.
Nick Radford,

@robertklep Non verrà catturato verrà chiamato per qualsiasi errore e non solo quello che ti aspetti? Penso che questo stile non funzionerà se stai cercando di affermare fallimenti.
TheCrazyProgrammer

1
@TheCrazyProgrammer il catchgestore dovrebbe probabilmente essere passato come secondo argomento a then. Tuttavia, non sono del tutto sicuro di quale fosse l'intenzione del PO, quindi l'ho lasciato così com'è.
robertklep,

1
E anche per chiunque sia curioso del gelsomino, usa done.fail('msg')in questo caso.
Paweł,

puoi dare un esempio completo di dove dovrebbero andare il codice principale e le asserzioni, non così chiaro qui ..... specialmente se stai affermando un valore dalla chiamata di servizio / promessa originale nel nostro codice. La promessa reale per il nostro codice rientra in quest'altra "promessa della moka" ??
bjm88,

10

Ho avuto questo errore quando stubing con sinon.

La soluzione consiste nell'utilizzare il pacchetto npm sinon-come-promesso quando si risolvono o si rifiutano le promesse con gli stub.

Invece di ...

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

Uso ...

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

C'è anche un metodo di risoluzione (nota la s alla fine).

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


1
Sinon ora include i metodi "risolve" e "rifiuta" per gli stub dalla versione 2. Vedere npmjs.com/package/sinon-as-promised . Tuttavia ho ancora fatto +1 sulla risposta, ma non lo sapevo.
Andrew

9

Le librerie di asserzioni in Moka funzionano generando un errore se l'asserzione non era corretta. Lanciare un errore si traduce in una promessa respinta, anche se lanciata nella funzione esecutore fornita al catchmetodo.

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

Nel codice sopra riportato l' erroroggetto viene valutato in truemodo tale che la libreria di asserzioni generi un errore ... che non viene mai rilevato. A causa dell'errore il donemetodo non viene mai chiamato. Il donecallback di Mocha accetta questi errori, quindi puoi semplicemente terminare tutte le catene di promesse in Mocha con .then(done,done). Questo assicura che il metodo done sia sempre chiamato e che l'errore venga riportato allo stesso modo di quando Mocha rileva l'errore dell'asserzione nel codice sincrono.

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

Do credito a questo articolo per l'idea di usare .then (fatto, fatto) quando testiamo le promesse in Mocha.


6

Per coloro che cercano l'errore / avviso UnhandledPromiseRejectionWarningal di fuori di un ambiente di test, potrebbe essere probabilmente perché nessuno nel codice si prende cura dell'errore finale in una promessa:

Ad esempio, questo codice mostrerà l'avviso riportato in questa domanda:

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

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

e l'aggiunta .catch()o la gestione dell'errore dovrebbe risolvere l'avviso / errore

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

O usando il secondo parametro nella thenfunzione

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

1
Certo, ma penso che nella vita reale di solito non usiamo solo new Promise((resolve, reject) => { return reject('Error reason!'); })ma in funzione, function test() { return new Promise((resolve, reject) => { return reject('Error reason!'); });}quindi all'interno della funzione non abbiamo bisogno di usare .catch()ma per gestire con successo gli errori è sufficiente usarlo quando si chiama quella funzione test().catch(e => console.log(e))o asincrono / try { await test() } catch (e) { console.log(e) }
attendi la

1

Ho affrontato questo problema:

(nodo: 1131004) UnhandledPromiseRejectionWarning: rifiuto della promessa non gestita (reection id: 1): TypeError: res.json non è una funzione (nodo: 1131004) DeprecationWarning: i rifiuti non gestiti sono respinti. In futuro, promesse di rifiuto che non vengono gestite interromperanno il processo di Node.j con un codice di uscita diverso da zero.

È stato un mio errore, stavo sostituendo l' resoggetto then(function(res), quindi ho cambiato il resrisultato e ora funziona.

Sbagliato

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

Correzione

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

Codice di servizio:

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

Ecco la mia esperienza di take con E7 async / await :

Nel caso in cui tu abbia async helperFunction()ricevuto una chiamata dal tuo test ... (una esplicita con la asyncparola chiave ES7 , intendo)

→ assicurati di chiamarlo così await helperFunction(whateverParams) (beh, sì, naturalmente, una volta che lo sai ...)

E affinché funzioni (per evitare "wait è una parola riservata"), la funzione di test deve avere un marker asincrono esterno:

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

4
Non è necessario chiamarlo come await helperFunction(...). Una asyncfunzione restituisce una promessa. Potresti semplicemente gestire la promessa restituita come faresti con una funzione non contrassegnata asyncche sembra restituire una promessa. Il punto è gestire la promessa, punto. Non importa se la funzione è asynco meno. awaitè semplicemente uno dei molteplici modi per gestire la promessa.
Louis,

1
Vero. Ma poi devo investire le linee sulla cattura ... o i miei test passano come falsi positivi e le promesse non mantenute rimarranno inosservate (in termini di risultati dei testrunner). Quindi aspettare mi sembra meno linee e sforzi. - Ad ogni modo, dimenticare l'aspettare era ciò che UnhandledPromiseRejectionWarningmi ha causato ... quindi questa risposta.
Frank Nocke,

0

Ho avuto un'esperienza simile con Chai-Webdriver per Selenium. Ho aggiunto awaitall'asserzione e risolto il problema:

Esempio con Cucumberjs:

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

-7

Ho risolto questo problema dopo aver disinstallato il webpack (reagire al problema js).

sudo uninstall webpack
Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.