Привет, меня зовут Крис, и я инженер по разработке фронтенда.
Это первая статья, которую я написал, и я надеюсь, что она будет полезной.
При разработке библиотек компонентов или плагинов часто требуется глобальная обработка ошибок для достижения таких целей:
- Глобальная унифицированная обработка ошибок;
- Подсказка сообщений об ошибках для разработчиков;
- Обработка падений программы и так далее.
Как это сделать?
Далее я кратко реализую метод обработки ошибок, а затем представлю процесс реализации исходного кода 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__
не упаковывается, что оптимизирует размер пакета.
Надеюсь, я смог прояснить ваши вопросы, и это принесет вам пользу. Если вам понравилось, не забудьте похлопать. Оставайтесь с нами! 🙂