无法在React服务器端呈现中处理UnhandledPromiseRejectionWarning

我有一个在客户端和服务器上呈现的React应用程序。 当任何组件的render()失败时(由于我的代码中有一个bug,例如试图读取一个未定义的对象的属性),那么如果我从浏览器的另一个页面导航,那么我得到一个完整的堆栈跟踪浏览器开发者控

但是,当我触发服务器端的相同代码的呈现(通过直接将浏览器指向包含有问题的组件的有问题的路由),那么我只是在服务器的控制台中得到这样的错误:

(node:97192) UnhandledPromiseRejectionWarning: Unhandled promise rejection (rejection id: 1): TypeError: Cannot read property 'tagDefinitionId' of undefined

没有堆栈跟踪,debugging有点困难。

问题: 在服务器端渲染过程中是否有一种方法可以覆盖所有的render()失败?

下面是触发服务器端呈现以响应请求的Node-Express端点的代码。 我相信renderToString()内部发生了未处理的承诺拒绝,但是这个函数返回一个string,而不是一个承诺。

`

 import configureStore from '../../redux/store'; import { renderToString } from 'react-dom/server'; import { Provider } from 'react-redux'; import React from 'react'; import RouterContext from 'react-router/lib/RouterContext'; import createMemoryHistory from 'react-router/lib/createMemoryHistory'; import match from 'react-router/lib/match'; import template from './template'; import routes from '../../routes'; const clientAssets = require(KYT.ASSETS_MANIFEST); app.use((request, response) => { const history = createMemoryHistory(request.originalUrl); match({ routes, history }, (error, redirectLocation, renderProps) => { if (error) { response.status(500).send(error.message); } else if (redirectLocation) { response.redirect(302, `${redirectLocation.pathname}${redirectLocation.search}`); } else if (renderProps) { // This is the initial store const store = configureStore(); // When a React Router route is matched then we render // the components and assets into the template. const render = () => { // Grab the initial state from our Redux store const initialState = JSON.stringify(store.getState()); let isNotFoundRoute = false; if (renderProps.routes[1].path === '*') { isNotFoundRoute = true; } // Populate the HTML document with the current redux store response.status(isNotFoundRoute ? 404 : 200).send(template({ root: renderToString( <Provider store={store}> <RouterContext {...renderProps} /> </Provider> ), cssBundle: clientAssets.main.css, jsBundle: clientAssets.main.js, initialState, })); }; // Fetch the components from the renderProps and when they have // promises, add them to a list of promises to resolve before starting // a HTML response fetchComponentData(store.dispatch, renderProps.components, renderProps.params).then(render); } else { response.status(404).send('Not found'); } }); }); 

`

我认为这里唯一可能与之相关的自定义代码是template.js:

`

 import Helmet from 'react-helmet'; export default (vo) => { const helmet = Helmet.rewind(); return ` <!DOCTYPE html> <html lang="en" ${helmet.htmlAttributes.toString()} > <head> ${helmet.title.toString()} <meta httpEquiv="X-UA-Compatible" content="IE=edge" /> <meta charSet='utf-8' /> <meta httpEquiv="Content-Language" content="en" /> <meta name="viewport" content="width=device-width, initial-scale=1" /> <meta name="theme-color" content="white" /> ${helmet.meta.toString()} ${helmet.link.toString()} <link id="favicon" rel="shortcut icon" href="/favicon.png" sizes="16x16 32x32" type="image/png" /> <link rel="manifest" href="manifest.json"> ${vo.cssBundle ? '<link rel="stylesheet" type="text/css" href="' + vo.cssBundle + '">' : ''} </head> <body ${helmet.bodyAttributes.toString()} > <div id="root" class="root"><div>${vo.root}</div></div> <script> window.__PRELOADED_STATE__ = ${vo.initialState} </script> <script async src="${vo.jsBundle}"></script> </body> </html> `; }; 

`

是的,你可以用来logging未处理拒绝的堆栈痕迹,但是你最好简单地处理它们。 事实上,当render函数中的某个东西抛出一个exception时,什么都没有发现。

我会build议重新构build您的脚本

 app.use((request, response) => { const history = createMemoryHistory(request.originalUrl); new Promise((resolve, reject) => { match({ routes, history }, (error, redirectLocation, renderProps) => { if (error) reject(error); else resolve({redirectLocation, renderProps}); }).then(({redirectLocation, renderProps}) => { if (redirectLocation) { return {status: 302, target: redirectLocation.pathname + redirectLocation.search}; } else if (!renderProps) { return {status: 404, content: 'Not found'}; } else { const store = configureStore(); return fetchComponentData(store.dispatch, renderProps.components, renderProps.params).then(() => { const initialState = JSON.stringify(store.getState()); const isNotFoundRoute = (renderProps.routes[1].path === '*'); const content = template({ root: renderToString( <Provider store={store}> <RouterContext {...renderProps} /> </Provider> ), cssBundle: clientAssets.main.css, jsBundle: clientAssets.main.js, initialState, }); return {status: isNotFoundRoute ? 404 : 200, content}; }); } }).catch(err => { // THIS IS IMPORTANT! console.error(err); // or err.message and err.stack and everything, maybe including route return {status: 500, content: 'Sorry, we\'ll look into it'}; // or err.message if you trust it }).then(({status, target, content}) => { if (status >= 300 && status < 400) response.redirect(status, target); else respose.status(status).send(content); if (status >= 400) console.debug(…) }); }); 

不幸的是,这听起来像是一个难以在React @ 15中处理的问题。 我能想到的唯一方法是将error handling添加到所有组件中的所有渲染方法,这是不可行的。

错误的问题和反应直到现在是任何错误将发送反应到一个不稳定的状态。 幸运的是, react@next (16)附带了componentDidCatch – 一个新的生命周期方法,可以捕获和处理组件或任何后代(子)中的任何错误。

您可以在React团队的博客文章中详细了解这种新行为。

希望这可以帮助!