Как изящно обрабатывать асинхронные ошибки JavaScript?

JavaScript

1. try/catch

try/catchВ основном самые распространенныеasync/awaitПри совместном использовании, в основном, мы будем использовать его для окружения большинства асинхронных методов.awaitпосле ключевого словаpromiseоднаждыreject, ошибка исключения будет брошена.

run();
async function run() {
    try {
        await Promise.ject(new Error('Oops!'));
    } catch (err) {
        console.error(error.message);
    }
}

try/catchОн также может обрабатывать синхронные ошибки, такие как следующие :

async function run() {
  const v = null;
  try {
    await Promise.resolve('foo');
    v.thisWillThrow;
  } catch (error) {
        // 会出现"TypeError: Cannot read property 'thisWillThrow' of null"
      console.error(error.message);
  }
}

returnвопрос

Вроде бы надо только всю логику в мозг засунутьtry/catchТам все хорошо? Не очень точно, но следующий код приведет кunhandled promise rejection. этоreturnКлючевое слово возвращается напрямую, и ошибка не обнаруживается:

async function run() {
    try {
        // 直接返回Promise,而不是用await关键字
        return Promise.reject(new Error('Oops!'));
    } catch (error) {
        console.error(error.message);        
    }
}

Решение здесь заключается в использованииreturn awaitрешать:

async function run() {
    try {
        return await Promise.reject(new Error('Oops!'));
    } catch (error) {
        console.error(error.message);        
    }
}

проблема с обратным вызовом

Другой вопросtry catchФункция обратного вызова не может быть захвачена. try catch работает только в одном контексте выполнения. Вот попытка поймать в обратном вызове, чтобы поймать ошибку:

setTimeout(funciton() {
  try {
    fn()
  } catch (e) {
      // handle error
  }          
           
})

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

2. Golang-style(then)

стиль golang готов к использованию.then()способ совмещенияpromiseПреобразовать в другой, который обрабатывает ошибкиreject promise. Вы можете использовать что-то вродеif(err)проверить:

async function throwAnError() {
    throw new Error('Opps!');
}

async function runAwait() {
    let err = await throwAnError();
    if (err){
       console.error(err.message);
    }
}

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

тогда () решение

async function runAwait() {
       let err = await throwAnError().then(() => null, err => err);
    if (err){
       console.error(err.message);
    }
}


then()Кстати, буду ждатьpromiseусловиеresolveилиrejectЗатем выполните соответствующий обратный вызов, а затем оценитеerrОбъект обрабатывается, поэтому он фактически эквивалентен захвату.

вернуть и ошибку, и значение

async function run() {
    let [err, res] = await throwAnError().then(v => [null, v], err => [err, null]);
    if (err){
        console.error(err.message);
    }
    console.log(res)
}

результат:

Это можно сделать путем деструктуризации, чтобы вернуть массив, содержащий результат иerrorобъект. Конечно, еслиrejectвернусьnullа такжеerrorОбъекты; и еслиresolvedВозвращает первое из массиваerrorобъектnull, второе - результат.

Преимущества и недостатки

  • Преимущества: с этим шаблоном можно обращаться более лаконично, устраняя при этом необходимость писатьcatch.
  • Недостаток 1: это очень повторяющийся и требует оценки каждый раз, когда выполняется асинхронная операция.errorобъект.
  • Недостаток 2: Не могу помочь с обработкойrunОшибка синхронизации в методе. Так что этот метод нужно использовать с осторожностью.

3. Поймать

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

async function run() {
  return Promise.reject(new Error('Oops!'));
}

run().catch(function handleError(err) {
    console.error(err.message);
}).catch( err => {
    process.nextTick(() => { throw errl});
})

использоватьcatchпоймать ошибку, еслиhandleErrorОшибки есть и в самих себе, и надо пере-catchснова, но во избежание回调地狱, который завершает процесс, если в методе возникает ошибка.

Преимущества и недостатки

  • использоватьcatchЕсли это так, он перехватит асинхронную ошибку независимо от того, перехватывает ли ее сам асинхронный метод.
  • использоватьtry/catchНеизбежныйcatchВыдает исключение само по себе, и если оно выдает, оно вложено еще на один уровеньtry/catchКроме того, лучше всего добавитьcatchсделать код более лаконичным.

4 Глобальный захват ошибок

4.1 Захват глобальных ошибок браузера

Глобальная обработка браузера в основном зависит от событий, потому что браузеры управляются событиями. Как только выдается ошибка, интерпретатор останавливает выполнение и расширяется в контексте среды выполнения, после чего будетonerrorГлобальное событие вызывает:

window.addEventListener('error', function (e) {
    var error = e.error;
    console.log(error);
})

Глобальный обработчик ошибок перехватывает любую ошибку, возникающую в среде выполнения, даже если это ошибка другого объекта или различные типы ошибок. Это распространенный способ обработки ошибок глобально и централизованно.

стек вызовов

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

window.addEventListener('error', function (e) {
  var stack = e.error.stack;
  var message = e.error.toString();
  if (stack) {
    message += '\n' + stack;
  }
  var xhr = new XMLHttpRequest();
  xhr.open('POST', '/log', true);
  // Fire an Ajax request with error details
  xhr.send(message);
});

По логу видно, что именно вызывает какую ошибку. Стек вызовов также может быть очень полезен при отладке. Вы можете проанализировать журнал и посмотреть, какие условия вызывают ошибку.

Уведомление:

Вы не увидите ошибку при междоменном сценарии. В JS сообщения об ошибках разрешены только в том же домене.

личная идея

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

4.2 Глобальный захват ошибок Node.js

Обработка исключений в Node.js сама по себе намного сложнее, поскольку включает в себя процесс или поток, создающий исключение.

Глобальная обработка ошибок на основе Koa

nodejs этоerror-firstМеханизм асинхронной обработки , где нижний уровень будет вызыватьnetмодульныйlistenметод и выполнить обратный вызов при возникновении ошибки.

app.listen(app.config.listenPort, (err) => {
  if (err) throw err
  app.logger.info(`> Ready on http://localhost:${app.config.listenPort}`)
})

обработка ошибок маршрутизации

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

router.get('/loginAuth', async (ctx, next) => {
  try {
    const code = query.code
    const res = await requestToken(code)
    if (res.data.code !== 0) {
      ctx.app.logger.error(`request token error.Code is ${res.data.code} || response is: ${JSON.stringify(res.data.data)} || msg: ${res.data.message}`)
      ctx.body = {
        code: 10000,
        message: `request token by code error`
      }
    } else {
      ctx.body = res.data
    }
  } catch (err) {
    ctx.app.logger.error(`request api has exception ${ctx.request.url} || ${err.code} ||  ${err.message} || ${err.stack}`)
    ctx.body = {
      code: 500,
      message: `Error response`
    }
  }
})

5. Резюме

  • Однако часто можно ожидать или превысить исключения, используйтеtry/catchнет проблем.
  • Для непредвиденных ошибок попробуйте использоватьcatchчтобы убедиться, что они будут пойманы.
  • добавить обработчик ошибок вwindowобъект, он будет ловить асинхронные ошибки, соответствующиеDRYа такжеSOLIDв общем. Глобальный обработчик ошибок может помочь вам поддерживать чистоту асинхронного кода.

Reference

async-await-error-handling

nodejs-v12-lts