NodeJS UnhandledPromiseRejectionWarning

139

Więc testuję komponent, który opiera się na emiterze zdarzeń. Aby to zrobić, wymyśliłem rozwiązanie wykorzystujące Promises z 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();
  });
});

Na konsoli otrzymuję komunikat „UnhandledPromiseRejectionWarning”, mimo że wywoływana jest funkcja odrzucania, ponieważ natychmiast wyświetla komunikat „AssertionError: Promise error”

(node: 25754) UnhandledPromiseRejectionWarning: Unhandled promise rejection (rejection id: 2): AssertionError: Promise error: oczekiwano, że {Object (message, showDiff, ...)} będzie fałszywy 1) powinien przejść z poprawnym zdarzeniem

A potem po 2 sekundach otrzymuję

Błąd: przekroczono limit czasu 2000 ms. Upewnij się, że wywołanie zwrotne done () jest wywoływane w tym teście.

Co jest jeszcze dziwniejsze, odkąd wykonano wywołanie zwrotne catch (myślę, że z jakiegoś powodu niepowodzenie potwierdzenia uniemożliwiło resztę wykonania)

Zabawne jest to, że jeśli skomentuję, assert.isNotOk(error...)test przebiega dobrze bez żadnego ostrzeżenia w konsoli. Wciąż „zawodzi” w tym sensie, że wykonuje złapanie.
Ale nadal nie mogę z obietnicą zrozumieć tych błędów. Czy ktoś może mnie oświecić?

Jzop
źródło
Myślę, że masz jeden dodatkowy zestaw nawiasów zamykających i parens w ostatnim wierszu. Usuń je i spróbuj ponownie.
Redu
4
To jest tak fajne, że nowe ostrzeżenie o nieobsługiwanym odrzuceniu znajduje błędy w prawdziwym życiu i oszczędza czas. Tyle tu wygrać. Bez tego ostrzeżenia twoje testy wygasłyby bez żadnego wyjaśnienia.
Benjamin Gruenbaum

Odpowiedzi:

167

Problem jest spowodowany tym:

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

Jeśli asercja się nie powiedzie, zgłosi błąd. Ten błąd spowoduje, że nikt done()nie zostanie wywołany, ponieważ wcześniej kod był błędny. To właśnie powoduje upływ czasu.

„Unhandled odrzucenie obietnica” jest również spowodowane nieudanym twierdzenie, ponieważ jeśli zostanie zgłoszony błąd w catch()procedurze obsługi, a nie jest kolejnym catch()obsługi , błąd będzie się połknięciu (jak wyjaśniono w tym artykule ). UnhandledPromiseRejectionWarningOstrzegawczy informujący o tym fakcie.

Ogólnie rzecz biorąc, jeśli chcesz przetestować kod oparty na obietnicy w Mocha, powinieneś polegać na tym, że sama Mocha może już obsługiwać obietnice. Nie powinieneś używać done(), ale zamiast tego zwróć obietnicę z testu. Mocha sama wyłapie wszelkie błędy.

Lubię to:

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
źródło
7
Dla ciekawskich dotyczy to również jaśminu.
Nick Radford,
@robertklep Czy catch nie zostanie wywołany z powodu jakiegokolwiek błędu, a nie tylko tego, którego się spodziewasz? Myślę, że ten styl nie zadziała, jeśli próbujesz potwierdzać porażki.
TheCrazyProgrammer
1
@TheCrazyProgrammer program catchobsługi powinien prawdopodobnie zostać przekazany jako drugi argument do then. Jednak nie jestem do końca pewien, jaki był zamiar PO, więc zostawiłem go tak, jak jest.
robertklep
1
A także dla każdego, kto jest ciekawy jaśminu, użyj done.fail('msg')w tym przypadku.
Paweł
czy możesz podać pełny przykład tego, gdzie powinien iść główny kod w porównaniu z asercjami, tutaj nie jest to takie jasne ..... zwłaszcza jeśli w naszym kodzie potwierdzasz wartość z pierwotnego wywołania usługi / obietnicy. Czy rzeczywista obietnica dotycząca naszego kodu jest zawarta w innej „obietnicy mokki”?
bjm88,
10

Otrzymałem ten błąd przy kiciu z sinonem.

Rozwiązaniem jest użycie pakietu npm sinon-zgodnie z obietnicą podczas rozwiązywania lub odrzucania obietnic z kodami pośredniczącymi.

Zamiast ...

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

Posługiwać się ...

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

Istnieje również metoda rozstrzygania (zwróć uwagę na s na końcu).

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

danday74
źródło
1
Sinon zawiera teraz metody „resolves” i „ odrzucs ” dla kodów pośredniczących od wersji 2. Zobacz npmjs.com/package/sinon-as-promised . Nadal jednak dałem +1 odpowiedzi - nie wiedziałem o tym.
Andrew
10

Biblioteki asercji w Mocha działają, generując błąd, jeśli asercja nie jest poprawna. Zgłoszenie błędu powoduje odrzucenie obietnicy, nawet jeśli zostanie zgłoszona w funkcji wykonawczej dostarczonej do catchmetody.

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

W powyższym kodzie obiekt, którego errorobiekcje są szacowane, truewięc biblioteka potwierdzeń zgłasza błąd ... który nigdy nie jest przechwytywany. W wyniku błędu donemetoda nigdy nie jest wywoływana. doneCallback Mocha akceptuje te błędy, więc możesz po prostu zakończyć wszystkie łańcuchy obietnic w Mocha za pomocą .then(done,done). Gwarantuje to, że gotowa metoda jest zawsze wywoływana, a błąd byłby zgłaszany w taki sam sposób, jak wtedy, gdy Mocha przechwytuje błąd asercji w kodzie synchronicznym.

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

I dać kredyt do tego artykułu na pomysł wykorzystania .Następnie (zrobione, zrobione) podczas testowania obietnic w Mocha.

Matthew Orlando
źródło
6

Dla tych, którzy szukają błędu / ostrzeżenia UnhandledPromiseRejectionWarningpoza środowiskiem testowym, może to być prawdopodobnie spowodowane tym, że nikt w kodzie nie zajmuje się ostatecznym błędem w obietnicy:

Na przykład ten kod pokaże ostrzeżenie zgłoszone w tym pytaniu:

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

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

a dodanie .catch()lub obsługa błędu powinno rozwiązać ostrzeżenie / błąd

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

Lub używając drugiego parametru w thenfunkcji

new Promise((resolve, reject) => {
  return reject('Error reason!');
}).then(null, () => { /* do whatever you want here */ });
gsalgadotoledo
źródło
1
Oczywiście, ale myślę, że w prawdziwym życiu zwykle nie używamy tylko, new Promise((resolve, reject) => { return reject('Error reason!'); })ale w funkcji, function test() { return new Promise((resolve, reject) => { return reject('Error reason!'); });}więc wewnątrz funkcji nie musimy używać, .catch()ale aby skutecznie obsługiwać błędy, wystarczy użyć podczas wywoływania tej funkcji test().catch(e => console.log(e))lub wersji async / try { await test() } catch (e) { console.log(e) }
await
1

Zmierzyłem się z tym problemem:

(węzeł: 1131004) UnhandledPromiseRejectionWarning: Nieobsłużone odrzucenie obietnicy (identyfikator odrzucenia: 1): TypeError: res.json nie jest funkcją (node: 1131004) DeprecationWarning: Nieobsłużone odrzucenia obietnicy są przestarzałe. W przyszłości odrzucenia obietnicy, które nie zostaną obsłużone, zakończą proces Node.j niezerowym kodem zakończenia.

To był mój błąd, wymieniłem resobiekt w then(function(res), więc zmieniony resna wynik i teraz działa.

Źle

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

Korekta

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

Kod serwisowy:

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
źródło
1

Oto moje doświadczenie z E7 async / await :

W przypadku, gdy masz połączenie async helperFunction()z testu ... ( asyncmam na myśli jedno ze słowem kluczowym ES7 )

→ upewnij się, że tak await helperFunction(whateverParams) też nazywasz (cóż, tak, oczywiście, kiedy już wiesz ...)

Aby to zadziałało (aby uniknąć „await jest słowem zastrzeżonym”), funkcja testowa musi mieć zewnętrzny znacznik asynchroniczny:

it('my test', async () => { ...
Frank Nocke
źródło
4
Nie musisz tego nazywać await helperFunction(...). asyncZwraca obietnicę. Możesz po prostu obsłużyć zwróconą obietnicę, tak jak zrobiłbyś to na funkcji nieoznaczonej, asyncktóra tak się składa, że ​​zwraca obietnicę. Chodzi o to, aby dotrzymać obietnicy, kropka. asyncNie ma znaczenia, czy funkcja jest, czy nie. awaitjest tylko jednym z wielu sposobów dotrzymania obietnicy.
Louis
1
Prawdziwe. Ale potem muszę zainwestować w wyłapywanie ... albo moje testy przejdą jako fałszywe trafienia, a wszelkie nieudane obietnice pozostaną niezotoryzowane (pod względem wyników testrunner). Więc czekanie wygląda dla mnie jak mniej linii i wysiłku. - Zresztą, zapomnienie o czekaniu spowodowało, że UnhandledPromiseRejectionWarningdla mnie… stąd ta odpowiedź.
Frank Nocke
0

Miałem podobne doświadczenia z Chai-Webdriver for Selenium. Dodałem awaitdo asercji i rozwiązałem problem:

Przykład przy użyciu Cucumberjs:

Then(/I see heading with the text of Tasks/, async function() {
    await chai.expect('h1').dom.to.contain.text('Tasks');
});
Richard
źródło
-9

Rozwiązałem ten problem po odinstalowaniu webpacka (reaguj na problem z js).

sudo uninstall webpack
Mr Fun
źródło