react中的錯誤處理

持續更新中react相關庫源碼淺析react

解決的兩個問題

  • 收集錯誤
  • 自定義事件實現try...catch錯誤捕獲功能同時增長Pause on exceptions功能

react\packages\shared\ReactErrorUtils.js

用於存儲錯誤以及是否發生錯誤的狀態
// Used by Fiber to simulate a try-catch.
let hasError: boolean = false;
let caughtError: mixed = null;
複製代碼
須要從新拋出的錯誤以及從新拋出的狀態
// Used by event system to capture/rethrow the first error.
let hasRethrowError: boolean = false;
let rethrowError: mixed = null;
複製代碼
reporter對象上的onError方法提供一個藉口改變錯誤的狀態以及存儲捕獲到的錯誤對象
const reporter = {
  onError(error: mixed) {
    hasError = true;
    caughtError = error;
  },
};
複製代碼
錯誤系統入口函數,用於捕獲第一個錯誤

invokeGuardedCallback的封裝,當執行完invokeGuardedCallback以後,經過hasError判斷是否發生了錯誤,並調用clearCaughtError返回錯誤對象,並清空hasError、caughtError。而後將返回的錯誤存儲到rethrowError,並改變hasRethrowError的狀態。git

function invokeGuardedCallbackAndCatchFirstError(name,func,context,a,b,c,d,e,f) {
    invokeGuardedCallback.apply(this, arguments);
    if (hasError) {
        const error = clearCaughtError();
        if (!hasRethrowError) {
          hasRethrowError = true;
          rethrowError = error;
        }
    }
}
export function clearCaughtError() {
  if (hasError) {
    const error = caughtError;
    hasError = false;
    caughtError = null;
    return error;
  } else {
    invariant(
      false,
      'clearCaughtError was called but no error was captured. This error ' +
        'is likely caused by a bug in React. Please file an issue.',
    );
  }
}
複製代碼
調用傳入函數func的準備

invokeGuardedCallbackImpl的封裝,利用applythis指向reporter,用於當invokeGuardedCallbackImpl函數發生錯誤的時候,調用reporter上的接口onError,將錯誤對象存儲到最外層ReactErrorUtils.js文件模塊的hasError、caughtError變量上。github

export function invokeGuardedCallback(name,func,context,a,b,c,d,e,f) {
    hasError = false;
    caughtError = null;
    invokeGuardedCallbackImpl.apply(reporter, arguments);
}
複製代碼
直接執行的funcinvokeGuardedCallbackImpl

invokeGuardedCallbackImpl在不一樣的環境下有不一樣的實現:chrome

  • 在生產環境下利用try ... catch捕獲錯誤,並存儲錯誤到hasError、caughtError上。
  • 在開發環境下利用自定義事件模擬了try ... catch功能,同時具有在開發工具中在全部異常發生的地方自動斷點的功能。若是用try ... catch,那麼try塊語句中出現的錯誤不會斷點暫停,由於這個異常被catch捕獲了,除非在chrome下將Pause on exceptions打開。

Because React wraps all user-provided functions in invokeGuardedCallback, and the production version of invokeGuardedCallback uses a try-catch, all user exceptions are treated like caught exceptions, and the DevTools won't pause unless the developer takes the extra step of enabling pause on caught exceptions.數組

下面主要分析invokeGuardedCallbackImplbash

react\packages\shared\invokeGuardedCallbackImpl.js

生產環境:利用try-catch實現錯誤捕獲

let invokeGuardedCallbackImpl = function(name,func,context,a,b,c,d,e,f){
  const funcArgs = Array.prototype.slice.call(arguments, 3);
  try {
    func.apply(context, funcArgs);
  } catch (error) {
    this.onError(error);
  }
};
複製代碼

arguments轉成數組,而後執行傳入的函數func,執行上下文指定爲傳入的context。若是func執行過程當中發生錯誤,拋出的錯誤被捕獲並經過this.onError傳入最外層的變量上。這裏的this指向的是reporterapp

開發環境:模擬try-catch

const fakeNode = document.createElement('react');
const invokeGuardedCallbackDev = function(name,func,context,a,b,c,d,e,f){
    let windowEvent = window.event;
    const windowEventDescriptor = Object.getOwnPropertyDescriptor(
        window,
        'event',
    );
    const funcArgs = Array.prototype.slice.call(arguments, 3);

    /**
    * 給window添加error事件監聽函數,未捕獲的錯誤都會通過這裏
    */
    let error;
    let didSetError = false;
    let isCrossOriginError = false;
    function handleWindowError(event) {
        error = event.error;
        didSetError = true;
        if (error === null && event.colno === 0 && event.lineno === 0) {
            // 當加載自不一樣域的腳本中發生語法錯誤時,爲避免信息泄露,語法錯誤的細節將不會報告,而代之簡單的"Script error."
            //  event:
            // message:錯誤信息(字符串)。可用於HTML onerror=""處理程序中的event。
            // source:發生錯誤的腳本URL(字符串)
            // lineno:發生錯誤的行號(數字)
            // colno:發生錯誤的列號(數字)
            // error:Error對象(對象)
            isCrossOriginError = true;
        }
        if (event.defaultPrevented) {
            if (error != null && typeof error === 'object') {
                try {
                    error._suppressLogging = true;
                } catch (inner) {
                    // Ignore.
                }
            }
        }
    }
    window.addEventListener('error', handleWindowError);

    /**
     * 利用createEvent建立自定義事件event,並利用initEvent指定名稱,而後給DOM fakeNode添加evtType事件監聽函數
     */
    let didError = true;
    const evtType = `react-${name ? name : 'invokeguardedcallback'}`;
    const evt = document.createEvent('Event');
    evt.initEvent(evtType, false/*false表示阻止該事件向上冒泡*/, false/*false表示該事件默認動做不可取消*/);
    function callCallback() {
        fakeNode.removeEventListener(evtType, callCallback, false);
        if (
            typeof window.event !== 'undefined' &&
            window.hasOwnProperty('event')
        ) {
            window.event = windowEvent;
        }

        func.apply(context, funcArgs);
        didError = false;
    }
    fakeNode.addEventListener(evtType, callCallback, false);

    /**
     * 手動觸發fakeNode上的evt事件,此時會先執行傳入的func函數,若是發生錯誤會觸發window上的error事件
     */
    fakeNode.dispatchEvent(evt);


    /**
     * 還原window.event
     */
    if (windowEventDescriptor) {
        Object.defineProperty(window, 'event', windowEventDescriptor);
    }

    /**
     * 調用onError記錄error
     */
    if (didError) {
        if (!didSetError) {
            error = new Error(
                'An error was thrown inside one of your components'
            );
        } else if (isCrossOriginError) {
            error = new Error(
                "A cross-origin error was thrown. React doesn't have access to ",
            );
        }
        this.onError(error);
    }

    // Remove our event listeners
    window.removeEventListener('error', handleWindowError);
};
invokeGuardedCallbackImpl = invokeGuardedCallbackDev;
複製代碼

chromePause on exceptions功能

操做: f12 -> Source Tab -> 點擊 Pause on exceptions 暫停圖標 -> 圖標變成藍色,代表啓用了在未捕獲到的異常出現的時候斷點的功能。less

勾選Pause On Caught Exceptions, 可以在捕獲到異常的狀況下也斷點。ide

try{
    throw'a exception';
}catch(e){
    console.log(e);
}
複製代碼

上面 try 裏面的代碼會遇到異常,可是後面的 catch 代碼可以捕獲該異常。若是是全部異常都中斷(勾選了Pause On Caught Exceptions),那麼代碼執行到會產生異常的 throw 語句時就會自動中斷;而若是是僅遇到未捕獲異常才中斷,那麼這裏就不會中斷。通常咱們會更關心遇到未捕獲異常的狀況。函數

相關文章
相關標籤/搜索