Nodo JS UnhandledPromiseRejectionWarning

134

Entonces, estoy probando un componente que se basa en un emisor de eventos. Para hacerlo, se me ocurrió una solución usando Promesas con 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();
  });
});

En la consola, obtengo un 'UnhandledPromiseRejectionWarning' a pesar de que se llama a la función de rechazo, ya que muestra instantáneamente el mensaje 'AssertionError: Promise error'

(nodo: 25754) UnhandledPromiseRejectionWarning: Rechazo de promesa no controlado (id de rechazo: 2): Error de promesa: Error de promesa: se espera que {Object (message, showDiff, ...)} sea falso 1) debe hacer la transición con el evento correcto

Y luego, después de 2 segundos me sale

Error: se excedió el tiempo de espera de 2000 ms. Asegúrese de que se llame a la devolución de llamada done () en esta prueba.

Lo cual es aún más extraño ya que se ejecutó la devolución de llamada catch (creo que por alguna razón el fallo de afirmación impidió el resto de la ejecución)

Ahora lo curioso, si comento que assert.isNotOk(error...)la prueba funciona bien sin ninguna advertencia en la consola. Todavía 'falla' en el sentido de que ejecuta la captura.
Pero aún así, no puedo entender estos errores con promesa. ¿Alguien puede iluminarme?

Jzop
fuente
Creo que tiene un conjunto adicional de llaves de cierre y parens en la última línea. Por favor, elimínelos e intente nuevamente.
Reducir
44
Esto es genial, la nueva advertencia de rechazo no controlada encuentra errores en la vida real y ahorra tiempo a las personas. Tanto ganar aquí. Sin esta advertencia, sus pruebas se habrían agotado sin ninguna explicación.
Benjamin Gruenbaum

Respuestas:

161

El problema es causado por esto:

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

Si la afirmación falla, arrojará un error. Este error hará que done()nunca se vuelva a llamar, porque el código erró antes. Eso es lo que causa el tiempo de espera.

El "rechazo de la promesa no manejada" también es causado por la afirmación fallida, porque si se arroja un error en un catch()controlador y no hay un catch()controlador posterior , el error se tragará (como se explica en este artículo ). losUnhandledPromiseRejectionWarning advertencia lo alerta sobre este hecho.

En general, si desea probar el código basado en promesas en Mocha, debe confiar en el hecho de que Mocha ya puede manejar las promesas. No debe usar done(), sino devolver una promesa de su prueba. Mocha entonces detectará cualquier error por sí mismo.

Me gusta esto:

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
fuente
77
Para cualquier persona curiosa, esto también es cierto para el jazmín.
Nick Radford
@robertklep ¿Will catch no recibirá ningún error y no solo el que está esperando? Creo que este estilo no funcionará si intentas afirmar fallas.
TheCrazyProgrammer
1
@TheCrazyProgrammer el catchcontrolador probablemente debería pasarse como segundo argumento a then. Sin embargo, no estoy completamente seguro de cuál era la intención del OP, así que lo dejé como está.
robertklep
1
Y también para cualquiera que tenga curiosidad por el jazmín, úselo done.fail('msg')en este caso.
Paweł
¿Puede dar un ejemplo completo de dónde deben ir el código principal frente a las aserciones? ¿La promesa real de nuestro código va dentro de esta otra "promesa mocha"?
bjm88
10

Recibí este error al tropezar con sinon.

La solución es usar el paquete npm sinon-as-prometido al resolver o rechazar promesas con apéndices.

En vez de ...

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

Utilizar ...

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

También hay un método de resolución (tenga en cuenta la s al final).

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

danday74
fuente
1
Sinon ahora incluye los métodos "resuelve" y "rechaza" para los talones a partir de la versión 2. Consulte npmjs.com/package/sinon-as-promised . Sin embargo, todavía hice +1 en la respuesta: no sabía sobre esto.
Andrew
9

Las bibliotecas de aserciones en Mocha funcionan arrojando un error si la aserción no era correcta. Lanzar un error da como resultado una promesa rechazada, incluso cuando se lanza en la función ejecutora proporcionada al catchmétodo.

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

En el código anterior, el errorobjetado se evalúa para trueque la biblioteca de aserciones arroje un error ... que nunca se detecta. Como resultado del error done, nunca se llama al método. La donedevolución de llamada de Mocha acepta estos errores, por lo que simplemente puede finalizar todas las cadenas de promesa en Mocha con .then(done,done). Esto garantiza que siempre se llame al método realizado y que el error se informe de la misma manera que cuando Mocha detecta el error de la aserción en 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);
});

Doy crédito a este artículo por la idea de usar .then (hecho, hecho) al probar promesas en Mocha.

Matthew Orlando
fuente
6

Para aquellos que buscan el error / advertencia UnhandledPromiseRejectionWarningfuera de un entorno de prueba, podría ser probablemente porque nadie en el código se está ocupando del eventual error en una promesa:

Por ejemplo, este código mostrará la advertencia informada en esta pregunta:

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

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

y agregar .catch()o manejar el error debería resolver la advertencia / error

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

O usando el segundo parámetro en la thenfunción

new Promise((resolve, reject) => {
  return reject('Error reason!');
}).then(null, () => { /* do whatever you want here */ });
gsalgadotoledo
fuente
1
Por supuesto, pero creo que en la vida real generalmente no usamos solo new Promise((resolve, reject) => { return reject('Error reason!'); })sino en función, function test() { return new Promise((resolve, reject) => { return reject('Error reason!'); });}por lo que no es necesario usar la función interna, .catch()sino que para manejar con éxito los errores, es suficiente para usar cuando se llama a esa función test().catch(e => console.log(e))o versión asíncrona / esperatry { await test() } catch (e) { console.log(e) }
mikep
1

Me enfrenté a este problema:

(nodo: 1131004) UnhandledPromiseRejectionWarning: rechazo de promesa no manejada (id de rechazo: 1): TypeError: res.json no es una función (nodo: 1131004) DeprecationWarning: los rechazos de promesa no manejados están en desuso. En el futuro, los rechazos de promesas que no se manejan terminarán el proceso de Node.j s con un código de salida distinto de cero.

Fue mi error, estaba reemplazando el resobjeto then(function(res), así que cambiéres al resultado y ahora está funcionando.

Incorrecto

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

Corrección

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 servicio:

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
fuente
1

Aquí está mi experiencia con E7 async / await :

En caso de que async helperFunction()reciba una llamada de su prueba ... (una de ellas con el ES7async palabra clave , quiero decir)

→ asegúrese de llamarlo como await helperFunction(whateverParams) así (bueno, sí, naturalmente, una vez que sepa ...)

Y para que eso funcione (para evitar 'esperar es una palabra reservada'), su función de prueba debe tener un marcador asíncrono externo:

it('my test', async () => { ...
Frank Nocke
fuente
44
No tienes que llamarlo como await helperFunction(...). Una asyncfunción devuelve una promesa. Puede manejar la promesa devuelta como lo haría en una función no marcada asyncque devuelve una promesa. El punto es manejar la promesa, punto. Si la función es asynco no, no importa. awaites simplemente una de las múltiples formas de manejar la promesa.
Louis
1
Cierto. Pero luego tengo que invertir líneas en la captura ... o mis pruebas pasan como falsos positivos, y cualquier promesa fallida quedará descartada (en términos de resultados de testrunner). Así que esperar me parece menos líneas y esfuerzo. - De todos modos, olvidando esperar fue, lo que UnhandledPromiseRejectionWarningme causó eso ... así que esta respuesta.
Frank Nocke
0

Tuve una experiencia similar con Chai-Webdriver para Selenium. Agregué awaita la afirmación y solucionó el problema:

Ejemplo usando Cucumberjs:

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

Resolví este problema después de desinstalar webpack (reaccionar problema js).

sudo uninstall webpack
Señor divertido
fuente