Должен ли я избегать try catch в каждом async / await на Node js? - PullRequest
0 голосов
/ 04 августа 2020

Это вопрос дизайна, который возник у меня во время модульного тестирования. Давайте погрузимся в пример:

Представьте себе:

async function foo() {
    try {
        return apiCall()
    }
    catch (e) {
        throw new CustomError(e);
    } 
}



async function bar() {
    return foo()
}



async function main() {
    try {
        await bar()
    }catch(e) {
        console.error(e)
    }
}

main()

Что мы здесь видим? Единственная функция, у которой нет блока try-catch, - это bar. Но если foo не удается, он должен быть пойман основным уловом.

При модульном тестировании, например,

describe('testing bar', () => {
    it('foo should throw', () => {
        foo.mockImplementantion(() => { throw new CustomError('error')});
        bar()
        .then((result) => console.log(result))
        .catch((err) => { exepect(err).toBeInstanceOf(CustomError)}) // this is what we are testing
    })
})

Мы видим, что Необработанное отклонение обещания вошел в консоль.

Итак, мой вопрос ... даже если я знаю, что main() поймает ошибку, следует ли мне использовать блок try-catch во всех функциях asyn c?

Ответы [ 3 ]

1 голос
/ 04 августа 2020

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

Если CustomError предпочтительнее, чем ошибка, которую может выдать apiCall, тогда try..catch необходимо, иначе - нет. Также проблема с foo заключается в том, что он обрабатывает только синхронные ошибки. Чтобы обработать отклоненные обещания, оно должно быть return await apiCall(), это известная ловушка async.

Неперехваченные отклонения нежелательны, в настоящее время они приводят к UnhandledPromiseRejectionWarning и, как ожидается, сработают sh Node в будущих версиях. Предпочтительно обрабатывать ошибку осмысленным образом на верхнем уровне, поэтому main необходимо перехватить ошибку. Это можно делегировать обработчику событий process uncaughtRejection, но для него может быть полезно сохранить дополнительный уровень обработки ошибок, который никогда не должен быть достигнут.

В результате мы видим, что Unhandled Отклонение обещания регистрируется в консоли.

Этого не должно происходить. Отклонение должно быть обработано тестом. Одна из возможных точек отказа объяснена выше: foo может возвращать исходную ошибку из apiCall вместо CustomError в случае, если он был неправильно смоделирован, это не соответствует ожиданиям и приведет к необработанному отклонению в catch(). Другой момент отказа заключается в том, что у теста есть несвязанное обещание, потому что оно не было возвращено, тест всегда проходит.

Асинхронный тест, который использует обещания, всегда должен возвращать обещание. Это можно улучшить, используя async..await. foo равно async, ожидается, что он всегда будет возвращать обещание:

it('foo should throw', async () => {
    foo.mockImplementantion(() => { return Promise.reject(new CustomError('error')) });
    await expect(bar()).rejects.toThrow(CustomError);
})

Теперь, даже если foo mock терпит неудачу (foo mock не повлияет на bar, если они определены в тот же модуль, как показано) и bar отклоняет с чем-то, что не CustomError, это будет подтверждено.

0 голосов
/ 05 августа 2020

Нет. Вам не нужно , чтобы использовать try / catch в каждом async / await. Вам только нужно , чтобы сделать это на верхнем уровне. В этом случае ваша main функция, которую вы уже выполняете.

Погода, которую вы должны , - это вопрос вашего мнения. Разработчики языка go достаточно серьезно относятся к этому, что стало стандартом в go, чтобы всегда обрабатывать ошибки при каждом вызове функции. Но это не является нормой для javascript или большинства других языков.

Необработанное отклонение обещания

Ваше необработанное отклонение обещания выбрасывается вашей функцией it(), потому что вы не говорите ей ждать для выполнения обещания.

Я предполагаю, что вы используете что-то вроде mocha для модульного теста (другие структуры могут работать иначе). В mocha есть два способа обработки асинхронных тестов:

  1. Вызов обратного вызова done - функция it() всегда будет вызываться с обратным вызовом done. Это зависит от вас, хотите ли вы использовать его или как в опубликованном коде не использовать его:

     describe('testing bar', () => {
         it('foo should throw', (done) => {
             foo.mockImplementantion(() => { throw new CustomError('error')});
             bar()
             .then((result) => {
                 console.log(result);
                 done(); // ------------- THIS IS YOUR ACTUAL BUG
              })
             .catch((err) => {
                 exepect(err).toBeInstanceOf(CustomError);
                 done(); // ------------- THIS IS YOUR ACTUAL BUG
             })
         })
     })
    
  2. Вернуть обещание. Если вы вернете обещание функции it(), mocha будет знать, что ваш код асинхронный, и ждать завершения:

     describe('testing bar', () => {
         it('foo should throw', (done) => {
             foo.mockImplementantion(() => { throw new CustomError('error')});
    
             return bar() // <----------- THIS WOULD ALSO FIX IT
             .then((result) => {
                 console.log(result);
              })
             .catch((err) => {
                 exepect(err).toBeInstanceOf(CustomError);
             })
         })
     })
    

Короче говоря, с вашим кодом все в порядке. . Но у вас есть ошибка в модульном тесте .

0 голосов
/ 05 августа 2020

Как сказал мне @Bergi, я опубликую здесь несколько решений

Я заключил функцию в блок try catch

1.

async function bar() {
    try{
       return foo()
    } catch (e) {
       throw e
    }
}
Перепишите тест
describe('testing bar', () => {
    it('foo should throw', (done) => {
        foo.mockImplementantion(() => { throw new CustomError('error')});
        bar()
        .then((result) => { throw result }) // this is because we are expecting an error, so if the promise resolves it's actually a bad sign.
        .catch((err) => { 
          exepect(err).toBeInstanceOf(CustomError)}) // this is what we are testing
          done();
    })
})
Используйте return в тестовом примере
describe('testing bar', () => {
    it('foo should throw', () => {
        foo.mockImplementantion(() => { throw new CustomError('error')});
        return bar()
        .then((result) => { throw result })
        .catch((err) => { exepect(err).toBeInstanceOf(CustomError)}) // this is what we are testing
    })
})
...