Обработка ошибок в Vue3

Привет, меня зовут Крис, и я инженер по разработке фронтенда.

Это первая статья, которую я написал, и я надеюсь, что она будет полезной.


При разработке библиотек компонентов или плагинов часто требуется глобальная обработка ошибок для достижения таких целей:

  • Глобальная унифицированная обработка ошибок;
  • Подсказка сообщений об ошибках для разработчиков;
  • Обработка падений программы и так далее.

Как это сделать?

Далее я кратко реализую метод обработки ошибок, а затем представлю процесс реализации исходного кода Vue3.

Версия Vue3 в данной статье — 3.0.11.

1. Обработка распространенных ошибок

Существует множество распространенных ошибок, таких как:

  • Синтаксические ошибки JS;
  • Ошибки Ajax-запроса;
  • Ошибки загрузки статических ресурсов;
  • ошибки обещания;
  • ошибки iframe;

Существует множество других способов.

1.1 window.onerror

При возникновении ошибки во время работы JS выполняется метод window.onerror():

window.onerror = function(message, source, lineno, colno, error) {
  console.log('error message:', {message, source, lineno, colno, error});
}
Вход в полноэкранный режим Выход из полноэкранного режима

Если эта функция возвращает true, выполнение обработчика событий по умолчанию предотвращается.

1.2 Обработка ошибок try…catch

Вы также можете обрабатывать ошибки через try...catch:

try {
  // do something
} catch (error) {
  console.error(error);
}
Вход в полноэкранный режим Выход из полноэкранного режима

Я не буду здесь вдаваться в подробности других методов.

1.3 Думать

А если подумать, в Vue3 тоже везде через try...catch обрабатываются ошибки?

Давайте посмотрим вместе.


2. Простая глобальная обработка ошибок

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

Например:

const errorHandling = (fn, args) => {
  let result;
  try{
    result = args ? fn(...args) : fn();
  } catch (error){
    console.error(error)
  }
  return result;
}
Войти в полноэкранный режим Выйти из полноэкранного режима

Попробуйте запустить:

const f1 = () => {
    console.log('[f1 running]')
    throw new Error('[f1 error!]')
}

errorHandling(f1);
/*
 output:
 [f1 running]
Error: [f1 error!]
    at f1 (/Users/Chris1993/www/a.js:14:11)
    at errorHandling (/Users/Chris1993/www/a.js:4:39)
    at Object.<anonymous> (/Users/Chris1993/www/a.js:17:1)
    at Module._compile (node:internal/modules/cjs/loader:1095:14)
    at Object.Module._extensions..js (node:internal/modules/cjs/loader:1147:10)
    at Module.load (node:internal/modules/cjs/loader:975:32)
    at Function.Module._load (node:internal/modules/cjs/loader:822:12)
    at Function.executeUserEntryPoint [as runMain] (node:internal/modules/run_main:81:12)
    at node:internal/main/run_main_module:17:47
*/
Войти в полноэкранный режим Выйти из полноэкранного режима

Когда вам нужно обработать ошибку, вы просто передаете этот метод в качестве параметра.

Но это слишком просто. В реальном бизнесе мы часто сталкиваемся с вложенными вызовами методов. Давайте попробуем это сделать:

const f1 = () => {
    console.log('[f1]')
    f2();
}

const f2 = () => {
    console.log('[f2]')
    f3();
}

const f3 = () => {
    console.log('[f3]')
    throw new Error('[f3 error!]')
}

errorHandling(f1)
/*
  output:
  [f1 running]
  [f2 running]
  [f3 running]
  Error: [f3 error!]
    at f3 (/Users/Chris1993/www/a.js:24:11)
    at f2 (/Users/Chris1993/www/a.js:19:5)
    at f1 (/Users/Chris1993/www/a.js:14:5)
    at errorHandling (/Users/Chris1993/www/a.js:4:39)
    at Object.<anonymous> (/Users/Chris1993/www/a.js:27:1)
    at Module._compile (node:internal/modules/cjs/loader:1095:14)
    at Object.Module._extensions..js (node:internal/modules/cjs/loader:1147:10)
    at Module.load (node:internal/modules/cjs/loader:975:32)
    at Function.Module._load (node:internal/modules/cjs/loader:822:12)
    at Function.executeUserEntryPoint [as runMain] (node:internal/modules/run_main:81:12)
*/
Войти в полноэкранный режим Выйти из полноэкранного режима

Вложенные вызовы также могут работать таким образом. Тогда в методе errorHandling необходимо реализовать различную логику обработки ошибок.

Далее давайте посмотрим, как это обрабатывается в исходном коде Vue3?


3. Обработка ошибок в Vue3

Vue3 реализуется в три шага:

Шаг 1: Реализовать методы обработки ошибок

Реализуйте два метода обработки глобальных ошибок в файле errorHandling.ts:

Использование:

callWithAsyncErrorHandling(
  handler,
  instance,
  ErrorCodes.COMPONENT_EVENT_HANDLER,
  args
)
Войти в полноэкранный режим Выход из полноэкранного режима

Реализация исходного кода:

// packages/runtime-core/src/errorHandling.ts

// Error handling synchronization method
export function callWithErrorHandling(
  fn: Function,
  instance: ComponentInternalInstance | null,
  type: ErrorTypes,
  args?: unknown[]
) {
  let res
  try {
    res = args ? fn(...args) : fn(); // Call the original method
  } catch (err) {
    handleError(err, instance, type)
  }
  return res
}

// Error handling asynchronous methods
export function callWithAsyncErrorHandling(
  fn: Function | Function[],
  instance: ComponentInternalInstance | null,
  type: ErrorTypes,
  args?: unknown[]
): any[] {
  // ...
  const res = callWithErrorHandling(fn, instance, type, args)
  if (res && isPromise(res)) {
    res.catch(err => {
      handleError(err, instance, type)
    })
  }
  // ...
}
Войти в полноэкранный режим Выход из полноэкранного режима

Метод callWithErrorHandling проще, с простым try...catch делает слой упаковки.

А метод callWithAsyncErrorHandling более интересен, придется иметь дело с целевым методом в качестве параметров к callWithErrorHandling, по его возвращению к Promise объекта. Обработка ошибок методом catch.

Шаг 2: Обработка ошибок

Далее реализуйте метод handleError():

// packages/runtime-core/src/errorHandling.ts

// Handling errors
export function handleError(
  err: unknown,
  instance: ComponentInternalInstance | null,
  type: ErrorTypes,
  throwInDev = true
) {
  // ...
  logError(err, type, contextVNode, throwInDev)
}

function logError(
  err: unknown,
  type: ErrorTypes,
  contextVNode: VNode | null,
  throwInDev = true
) {
  // ...
  console.error(err)
}
Вход в полноэкранный режим Выход из полноэкранного режима

Здесь мы просто реализуем метод logError() и выводим содержимое ошибки напрямую через console.error(err).

Шаг 3: Реализовать встряхивание деревьев

// packages/runtime-core/src/errorHandling.ts

function logError(
  err: unknown,
  type: ErrorTypes,
  contextVNode: VNode | null,
  throwInDev = true
) {
  if (__DEV__) {
    // ...
  } else {
    console.error(err)
  }
}
Войдите в полноэкранный режим Выход из полноэкранного режима

При компиляции в производственную среду код ветви __DEV__ не упаковывается, что оптимизирует размер пакета.


Надеюсь, я смог прояснить ваши вопросы, и это принесет вам пользу. Если вам понравилось, не забудьте похлопать. Оставайтесь с нами! 🙂

Оцените статью
devanswers.ru
Добавить комментарий