Как ловко ловить ошибки без try catch

React.js
Как ловко ловить ошибки без try catch

это несколькоfeatureМагический эффект, достигаемый при совместном применении, вReactОн широко используется в исходном коде.

Когда я прочитал исходный код и увидел это, мое настроение изменилось:

Сбит с толку -- Сбит с толку -- Задумчив -- Проверяю документацию -- Внезапно просветлен

Прочитав эту статью, я думаю, вы тоже вздохнете:

Ты еще можешь так играть?

источник

мы знаем,ReactЕсть особенность вError Boundary, помогите нам в组件Пользовательский интерфейс, который отображает «Статус ошибки» при возникновении ошибки.

Для реализации этой функции необходимо отлавливать ошибки.

так вReactВ исходном коде все用户代码Все завернуто в метод.

Аналогично следующему:

function wrapper(func) {
  try {
    func();
  } catch(e) {
    // ...处理错误
  }
}

например, запускcomponentDidMountВремя:

wrapper(componentDidMount);

Все было идеально, ноReactКак интерфейсный фреймворк мирового уровня, он имеет широкую аудиторию и уделяет внимание максимальному во всем.

Нет, кто-то поднял вопрос:

Вы здесьtry catchвыполнить в用户代码позволит инструментам отладки браузераPause on exceptionsневерный.

Плюсы и минусы паузы при сбое исключений

Pause on exceptionsчто это такое?

Он инструмент отладки браузераsourceФункция панели.

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

Например, выполните следующий код и включите эту функцию:

let a = c;

Выполнение кода будет приостановлено на этой строке.

Эта функция может быть очень удобной, чтобы помочь нам найти未捕获的错误место, где это произошло.

Однако, когдаReactБуду用户代码завернут вtry catch, даже если код выдаст ошибку, это будетcatch.

Pause on exceptionsне могу выкинуть ошибку用户代码приостановлено, потому чтоerrorБылReact catch.

если мы не откроем дальшеPause on caught exceptions.

Включите эту функцию, чтобы код в捕获的错误Место, где произошла пауза.

Как решить

Для пользователя я пишу вcomponentDidMountКод явно не ловит ошибку, но когда ошибка возникаетPause on exceptionsЭто не работает, что действительно сбивает с толку.

Итак, в производственных условияхReactпродолжать использоватьtry catchвыполнитьwrapper.

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

  • захватывать用户代码выдает ошибку так чтоError BoundaryФункция работает

  • не захватывать用户代码выдает ошибку так чтоPause on exceptionsнедействительный

Эта, казалось бы, противоречивая функция,ReactКак сделать это с умом?

Как «ловить» ошибки

Давайте сначала реализуем первый пункт: захват用户代码Ошибка выброшена.

но не могу использоватьtry catch, потому что это сделало быPause on exceptionsневерный.

Решение: мониторwindowизerrorмероприятие.

согласно сGlobalEventHandlers.onerror MDN, событие может прослушивать два типа ошибок:

  • js ошибки времени выполнения (включая синтаксические ошибки).windowвызоветErrorEventинтерфейсerrorмероприятие

  • ресурсы (например,<img>или<script>) не удалось загрузить ошибку. Элемент, который загружает ресурс, сработаетEventинтерфейсerrorсобытия, которые могут бытьwindowпоймать ошибку на

реализация среды разработкиwrapperDev:

// 开发环境wrapper
function wrapperDev(func) {
  function handleWindowError(error) {
    // 收集错误交给Error Boundary处理
  }

  window.addEventListener('error', handleWindowError);
  func();
  window.removeEventListener('error', handleWindowError);
}

когдаfuncВо время выполнения выдается ошибка, она будетhandleWindowErrorиметь дело с.

Однако по сравнению с производственной средойwrapperPrdВнутриfuncВыданная ошибка будетcatch, не повлияет на последующее выполнение кода.

function wrapperPrd(func) {
  try {
    func();
  } catch(e) {
    // ...处理错误
  }
}

среда разработкиfuncПри возникновении ошибки выполнение кода будет прервано.

Например, выполните следующий код,finishбудет напечатано.

wrapperPrd(() => {throw Error(123)})
console.log('finish');

Но при выполнении следующего кода выполнение кода прерывается,finishне будет напечатано.

wrapperDev(() => {throw Error(123)})
console.log('finish');

как не поймать用户代码Как можно не прерывать выполнение последующего кода, если возникает ошибка?

Как обеспечить бесперебойное выполнение кода

Ответ: черезdispatchEventвызывать事件回调,существует回调вызывать用户代码.

согласно сEventTarget.dispatchEvent MDN:

отличный отDOMСобытия, запускаемые узлом (например,clickсобытие) обратный вызов выполняетсяevent loopЗапускается асинхронно.

пройти черезdispatchEventИнициированное событие запускается синхронно и вызывается в обратном вызове события.错误Это не повлияетdispatchEventзвонивший из .

Продолжаем трансформироватьсяwrapperDev.

Сначала создайте фиктивноеDOMУзлы, объекты событий, фиктивные типы событий:

// 创建虚构的DOM节点
const fakeNode = document.createElement('fake');
// 创建event
const event = document.createEvent('Event');
// 创建虚构的event类型
const evtType = 'fake-event';

Инициализируйте объект события и прослушивайте события. Вызывается в обратном вызове события用户代码. триггерное событие:

function callCallback() {
  fakeNode.removeEventListener(evtType, callCallback, false); 
  func();
}

// 监听虚构的事件类型
fakeNode.addEventListener(evtType, callCallback, false);

// 初始化事件
event.initEvent(evtType, false, false);

// 触发事件
fakeNode.dispatchEvent(event);

Полный процесс выглядит следующим образом:

function wrapperDev(func) {
  function handleWindowError(error) {
    // 收集错误交给Error Boundary处理
  }
  
  function callCallback() {
    fakeNode.removeEventListener(evtType, callCallback, false); 
    func();
  }
  
  const event = document.createEvent('Event');
  const fakeNode = document.createElement('fake');
  const evtType = 'fake-event';

  window.addEventListener('error', handleWindowError);
  fakeNode.addEventListener(evtType, callCallback, false);

  event.initEvent(evtType, false, false);
  

  fakeNode.dispatchEvent(event);
  
  window.removeEventListener('error', handleWindowError);
}

Когда мы звоним:

wrapperDev(() => {throw Error(123)})

будет выполняться последовательно:

  1. dispatchEventвызвать обратный вызов событияcallCallback

  2. существуетcallCallbackвыполнить доthrow Error(123), выдает ошибку

  3. callCallbackВыполнение прерывается, но вызывающая его функция продолжает выполнение.

  4. Error(123)одеялоwindow error handlerзахват дляError Boundary

где шаг 2 делаетPause on exceptionsне подведет.

Шаги 3 и 4 позволяют отловить ошибку, не препятствуя последующему выполнению кода, имитируяtry catchЭффект.

Суммировать

должен сказать,ReactЭта волна операции действительно деликатная.

наша миниатюраwrapperЕсть еще много недостатков, таких как:

  • Нет совместимости для разных браузеров

  • Запущен без учета других теговwindow error handler

ReactПолная версия исходного кодаwrapper,Видетьздесь