Почему многослойные асинхронные функции не улавливают ошибку, выданную на самом низком уровне в узле? - PullRequest
1 голос
/ 04 апреля 2019

Я пытаюсь проверить режим сбоя какого-либо почтового кода, который на самом низком уровне может выдать ошибку. Все слои между тестом и функцией, которые выдают, являются асинхронными и используют await для функций под ними. На верхнем уровне (также в асинхронной функции у меня есть блок try catch. Однако узел генерирует исключение необработанного обещания до того, как ошибка достигнет этого уровня.

Мой тестовый код выглядит так

beforeEach(function() {
  //set default values - tests can change them
  this.reasons = '';
  this.reschedules = 0;
  this.params.cid = 35124;

  this.startTest = async () => {
    /*  this.confirmation is an async function under test, 
        this.mailer is a mock mailer with an async "send" method
        which will throw an error in the correct test */
    const doner = this.confirmation(this.mailer);  
    // ..other actions related to mocking database access made by confirmation
    await doner;
    return this.mailer.maildata; //provide info on parameters passed to this.mailer
  };
});
it('Failure to send is reported', async function() {
  this.mailer.sendResolve = false; //tell mock mailer to fail send request
  try {
    await this.startTest();
    expect(true).to.be.false;
  } catch(err) {
    expect(err).to.be.instanceOf(Error);
  }
});

макет немного похож на этот

class Mailer {
    constructor(user,params){
...
    }
    ...
    async send(subject, to, cc, bcc) {
      this.maildata.subject = subject;
      if (to !== undefined) this.maildata.to = to;
      if (cc !== undefined) this.maildata.cc = cc;
      if (bcc !== undefined) this.maildata.bcc = bcc;
      if (!this.sendResolve) throw new Error('Test Error');
    }
    ...
 }

и краткое содержание тестируемого кода

 module.exports = async function(mailer) {
    //get confirm data from database
    const cData = await confirm(mailer.params.cid, mailer.db);
    if (cData.count > 0) {
       // ... format the email message and build it into maildata
       await mailer.send(
        subject,
        emailAddress,
        null,
        process.env.PAS_MAIL_FROM,
        {
          pid:cData.pid,
          type: 'confirmation',
          extra: `Calendar ID ${mailer.params.cid} with procedure ${cData.procedure}`
        }
      );
      debug('message sent, update the database');
      await mailer.db.exec(async connection => {
 ...
       });
      debug('success');
    } else {
      debug('invalid calendarid');
      throw new Error('Invalid Calendar ID');
    }
  };

Как видно, путь вызова из функции async send, которая отбрасывает стек обратно в try {}catch(){}, - все это асинхронные функции. Но когда я запускаю этот тестовый узел, выводится необработанное отклонение обещания.

Я попытался использовать отладчик кода Visual Studio для одного шага через это, я немного заблудился в механизме, который оборачивает асинхронные функции, превращая их в провайдеров обещаний. Насколько я вижу, один уровень ошибок обрабатывается правильно, а затем терпит неудачу на следующем уровне вверх.

Значит ли это, что каждая асинхронная функция должна иметь блок try catch, чтобы перехватывать и перебрасывать любую ошибку? Я не могу найти никакого объяснения, которое говорит, что я должен это сделать.

1 Ответ

0 голосов
/ 04 апреля 2019

Чтобы ответить на ваш вопрос:

Означает ли это, что каждая асинхронная функция должна иметь блок try catch, чтобы перехватывать и перебрасывать любую ошибку?

Ошибки распространяются через await вызовы, как вы и ожидали:

const assert = require('assert');

const outer = async () => {
  await middle();
}

const middle = async () => {
  await inner();
}

const inner = async () => {
  throw new Error('something bad happened');
}

it('should catch the error', async () => {
  let errorMessage;
  try {
    await outer();
  }
  catch (err) {
    errorMessage = err.message;
  }
  assert(errorMessage === 'something bad happened');  // Success!
});

... так что нет, вам не нужен блок try / catch на каждомуровень.


Отслеживание необработанных Promise отклонений

Я не могу точно увидеть, где цепочка await может быть нарушена в коде из вашегопример, но чтобы помочь отследить необработанные отклонения Promise, вы можете добавить обработчик процесса для события unhandledRejection , посмотреть зарегистрированный Promise, чтобы увидеть, где началось отклонение, и отследить обратный ход вызова.стек оттуда:

const assert = require('assert');

const outer = async () => {
  await middle();
}

const middle = async () => {
  inner();  // <= this will cause an Unhandled Rejection
}

const inner = async () => {
  throw new Error('something bad happened');
}

it('should catch the error', async () => {
  let errorMessage;
  try {
    await outer();
  }
  catch (err) {
    errorMessage = err.message;
  }
  assert(errorMessage === undefined);  // Success!  (broken await chain)
})

process.on('unhandledRejection', (reason, p) => {
  console.log('Unhandled Rejection at:', p);
  console.log('reason:', reason);
});

... который в этом случае записывает:

Unhandled Rejection at: Promise {
  <rejected> Error: something bad happened
      at inner (.../code.test.js:12:9)
      at inner (.../code.test.js:8:3)
      at middle (.../code.test.js:4:9)  // <= this is the broken link
      at Context.outer (.../code.test.js:18:11)
      at callFn (...\node_modules\mocha\lib\runnable.js:387:21)
      ...

... что указывает нам на Error, брошенный в inner, ипрослеживая цепочку, мы находим middle как разорванное звено.

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...