Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

项目异常捕获 #44

Open
into-piece opened this issue Apr 27, 2021 · 2 comments
Open

项目异常捕获 #44

into-piece opened this issue Apr 27, 2021 · 2 comments

Comments

@into-piece
Copy link
Owner

into-piece commented Apr 27, 2021

crm是一个多tag页面的SPA项目,打包发布完,用户在没有刷新页面的情况下使用会偶尔出现白屏或一直loading无响应,控制台出现load chunk fail error的报错,分析之后是因为切换页面会发起对对应页面分包代码资源的请求,而当前旧资源已经被清除了。

我引入了ErrorBoundary组件,通过新增的staic getderivedstatefromerror和componentDidcatch生命周期显示备用的样式fallback UI。当我用staic getderivedstatefromerror获取error堆栈信息,设置state中isError为true显示对应友好的报错页面,componentDidcatch进行错误上报,心满意足地觉得毫无破绽的时候,发现其实是没办法捕获到加载分包失败的错误的。

异常捕获是在渲染过程中workloop进行捕获的,而接口报错和事件处理器onClick对应回调内部的js报错则无法捕捉。

这个时候我们可以根据加载分包的loading组件来设置定时,若加载超过一定限制则报错,通过全局状态库保存error值,errorboundry组件获取值进行错误显示,显示替补样式,提醒用户进行刷新。

  const [isTimeout, setIsTimeout] = useState(false);
  useEffect(() => {
    let timer: NodeJS.Timeout;
    if (error) {
      // @ts-ignore
      clearInterval(timer);
    }
    if (!isTimeout) {
      timer = setTimeout(() => {
        setIsTimeout(true);
      }, 4000);
    }
    return () => {
      clearTimeout(timer);
    };
  }, [isTimeout, error]);

但又出现了新问题,我们多tag模式下刷新的话导致其他页面一起刷新,又要重新打开页面录入一堆信息,是你恼火不恼火?那么能不能刷新单个页面吗,重新请求当前页面的分包呢?但是我发现请求失败了如何重新请求分包呢?

我们用的是umi框架,进入源码可以看到代码分割的库是loadable,又是熟悉的老朋友,看一下代码分割实现的源码

umi/packages/runtime/src/dynamic/loadable.js

  const LoadableComponent = (props, ref) => {
    init();

    const context = useContext(LoadableContext);
    const state = useSubscription(subscription);

    useImperativeHandle(ref, () => ({
      retry: subscription.retry,
    }));

    if (context && Array.isArray(opts.modules)) {
      opts.modules.forEach((moduleName) => {
        context(moduleName);
      });
    }

    if (state.loading || state.error) {
      if (process.env.NODE_ENV === 'development' && state.error) {
        console.error(`[@umijs/runtime] load component failed`, state.error);
      }
      return createElement(opts.loading, {
        isLoading: state.loading,
        pastDelay: state.pastDelay,
        timedOut: state.timedOut,
        error: state.error,
        retry: subscription.retry,
      });
    } else if (state.loaded) {
      return opts.render(state.loaded, props);
    } else {
      return null;
    }
  };

  const LoadableComponentWithRef = forwardRef(LoadableComponent);
  // add static method in React.forwardRef
  // https://github.com/facebook/react/issues/17830
  LoadableComponentWithRef.preload = () => init();
  LoadableComponentWithRef.displayName = 'LoadableComponent';

  return LoadableComponentWithRef;
}

原来已经暴露了error信息和retry方法,利用retry方法我们可以提供重新刷新当前页面的能力,来重新请求当前分包。

@into-piece
Copy link
Owner Author

当然我们可以用lazy+suspense进行代码分割,这也是react官方推荐的方法,suspense组件fallback属性,可以设置对应的备用UI

@into-piece
Copy link
Owner Author

into-piece commented Apr 27, 2021

那react中没有两个生命周期组件,我们需要如何捕获项目中的异常呢?sentry的实现方法?

  1. try catch
  2. window.onError:setTimeout的报错是try catch无法捕获的,但可以用window.onError捕获
  3. Window: unhandledrejection event:promise的报错是封存在内部的,外部无法感知,我们可以监听unhandledrejection事件,当Promise 被 reject 且没有 reject 处理器的时候,会触发 unhandledrejection 事件

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

No branches or pull requests

1 participant