NodeJS UnhandledPromiseRejectionWarning

135

Итак, я тестирую компонент, который полагается на эмиттер событий. Для этого я придумал решение, использующее 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: ошибка обещания».

(узел: 25754) UnhandledPromiseRejectionWarning: необработанное отклонение обещания (идентификатор отклонения: 2): AssertionError: ошибка обещания: ожидается, что {Object (message, showDiff, ...)} будет ложным 1) должен перейти с правильным событием

А потом, через 2 секунды, я получаю

Ошибка: превышено время ожидания 2000 мс. Убедитесь, что в этом тесте вызывается обратный вызов done ().

Что еще более странно, поскольку был выполнен обратный вызов catch (я думаю, что по какой-то причине ошибка assert помешала остальной части выполнения)

А теперь самое забавное: если я закомментирую assert.isNotOk(error...)тест, он будет работать нормально, без предупреждения в консоли. Он по-прежнему «терпит неудачу» в том смысле, что выполняет захват.
Но все же я не могу понять эти ошибки с обещанием. Может кто меня просветить?

Jzop
источник
Я думаю, у вас есть еще один дополнительный набор закрывающих скобок и скобок в самой последней строке. Удалите их и попробуйте еще раз.
Реду
4
Это так здорово, новое предупреждение о необработанном отказе обнаруживает ошибки в реальной жизни и экономит время людей. Здесь столько выигрыша. Без этого предупреждения время ваших тестов истекло бы без каких-либо объяснений.
Бенджамин Грюнбаум

Ответы:

162

Проблема вызвана следующим:

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

Если утверждение терпит неудачу, оно выдаст ошибку. Эта ошибка приведет к тому, что done()никогда не будет вызвана, потому что код был ошибочным до нее. Вот что вызывает тайм-аут.

«Неизвестное отказ обещания» также вызвано неудавшимся утверждением, потому что если ошибка возникают в catch()обработчике, и не существует последующий catch()обработчик , ошибка будет получить проглатывание (как описано в этой статье ). UnhandledPromiseRejectionWarningПредупреждение предупреждает вас этот факт.

В общем, если вы хотите протестировать код на основе обещаний в Mocha, вы должны полагаться на тот факт, что сам Mocha уже может обрабатывать обещания. Вы не должны использовать done(), а вместо этого верните обещание из своего теста. Тогда Mocha сам поймает любые ошибки.

Как это:

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
источник
7
Для всех любопытных это верно и для жасмина.
Ник Рэдфорд
@robertklep Не поймаешь, вызовут любую ошибку, а не только ту, которую вы ожидаете? Я думаю, что этот стиль не сработает, если вы пытаетесь констатировать неудачи.
TheCrazyProgrammer
1
@TheCrazyProgrammer catchобработчик, вероятно, должен быть передан в качестве второго аргумента then. Однако я не совсем уверен, в чем заключалась цель OP, поэтому я оставил все как есть.
robertklep 08
1
А также для тех, кто интересуется жасмином, используйте done.fail('msg')в этом случае.
Paweł
Можете ли вы привести полный пример того, где должен идти основной код и утверждения, здесь не так ясно… особенно если вы утверждаете значение из исходного вызова службы / обещания в нашем коде. Соответствует ли реальное обещание нашего кода этому другому «обещанию мокко» ??
bjm88 07
10

Получил эту ошибку при заглушке с синоном.

Исправление состоит в том, чтобы использовать npm-пакет sinon-as-обещанный при разрешении или отклонении обещаний с заглушками.

Вместо того ...

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

Используйте ...

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

Также существует метод resolves (обратите внимание на s в конце).

См. Http://clarkdave.net/2016/09/node-v6-6-and-asynchronously-handled-promise-rejection

danday74
источник
1
Sinon теперь включает методы «решает» и «отклоняет» для заглушек, начиная с версии 2. См. Npmjs.com/package/sinon-as-promised . Я все же поставил +1 к ответу - я не знал об этом.
Эндрю
9

Библиотеки утверждений в Mocha работают, выкидывая ошибку, если утверждение было неверным. Выдача ошибки приводит к отклонению обещания, даже если оно выбрано функцией исполнителя, предоставленной catchметоду.

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

В приведенном выше коде errorобъект оценивается trueтак, что библиотека утверждений выдает ошибку ... которая никогда не перехватывается. В результате ошибки doneметод никогда не вызывается. doneОбратный вызов Mocha принимает эти ошибки, поэтому вы можете просто завершить все цепочки обещаний в Mocha с помощью .then(done,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);
});

Я отдаю должное этой статье за идею использования .then (done, done) при тестировании обещаний в Mocha.

Мэтью Орландо
источник
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 */ });
gsalgadotoledo
источник
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))или версии async / awaittry { await test() } catch (e) { console.log(e) }
mikep
1

Я столкнулся с этой проблемой:

(узел: 1131004) UnhandledPromiseRejectionWarning: отклонение необработанного обещания (идентификатор отклонения: 1): TypeError: res.json не является функцией (узел: 1131004) DeprecationWarning: необработанные отклонения обещаний устарели. В будущем, отклонения обещаний, которые не обрабатываются, завершат процесс Node.j с ненулевым кодом выхода.

Это была моя ошибка, я заменял resобъект в then(function(res), поэтому изменилres на результат, и теперь он работает.

Неправильно

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 / await :

Если у вас есть async helperFunction()звонок из вашего теста ... ( asyncя имею в виду одно явно с ключевым словом ES7 )

→ убедитесь, что вы называете это так await helperFunction(whateverParams) (ну да, естественно, как только вы узнаете ...)

И для того, чтобы это сработало (чтобы избежать «ожидание - это зарезервированное слово»), ваша тестовая функция должна иметь внешний маркер async:

it('my test', async () => { ...
Фрэнк Нок
источник
4
Вы не должны называть это как await helperFunction(...). asyncФункция возвращает обещание. Вы можете просто обработать возвращенное обещание, как если бы вы поступили с не отмеченной функцией, asyncкоторая возвращает обещание. Дело в том, чтобы выполнить обещание, и точка. Есть функция asyncили нет, не имеет значения. awaitэто всего лишь один из множества способов выполнить обещание.
Луи
1
Правда. Но тогда я должен вложить средства в отлов ... или мои тесты пройдут как ложные срабатывания, и любые невыполненные обещания останутся незамеченными (с точки зрения результатов тестировщика). Так что для меня ожидание выглядит как меньше строк и усилий. - Во всяком случае, это было причиной того, что UnhandledPromiseRejectionWarningя забыл об ожидании ... таким образом, этот ответ.
Фрэнк Нок
0

У меня был аналогичный опыт с Chai-Webdriver для Selenium. Я добавил awaitк утверждению, и оно устранило проблему:

Пример использования Cucumberjs:

Then(/I see heading with the text of Tasks/, async function() {
    await chai.expect('h1').dom.to.contain.text('Tasks');
});
Ричард
источник
-7

Я решил эту проблему после удаления веб-пакета (проблема с реакцией js).

sudo uninstall webpack
Мистер Fun
источник