diff --git a/packages/react-server/core/ClientController.js b/packages/react-server/core/ClientController.js index 4f2bb3535..4e1dd8f64 100644 --- a/packages/react-server/core/ClientController.js +++ b/packages/react-server/core/ClientController.js @@ -529,6 +529,17 @@ class ClientController extends EventEmitter { // These resolve with React elements when their data // dependencies are fulfilled. var elementPromises = PageUtil.standardizeElements(page.getElements()); + var timeoutDfd = []; + var elementPromisesOr = elementPromises.map((promise, index) => { + var orPromise = Q.defer(); + timeoutDfd[index] = Q.defer(); + + promise.then(orPromise.resolve); + promise.catch(orPromise.reject); + timeoutDfd[index].promise.catch(orPromise.reject); + + return orPromise.promise; + }); // These resolve with DOM mount points for the elements. // @@ -672,7 +683,7 @@ class ClientController extends EventEmitter { // Always render in order to proritize content higher in the // page. // - elementPromises.reduce((chain, promise, index) => chain + elementPromisesOr.reduce((chain, promise, index) => chain .then(() => promise .then(element => rootNodePromises[index] .then(root => renderElement(element, root, index)) @@ -691,7 +702,14 @@ class ClientController extends EventEmitter { // Look out for a failsafe timeout from the server on our // first render. if (!this._previouslyRendered){ - this._failDfd.promise.then(retval.resolve); + this._failDfd.promise.then(() => { + elementPromises.forEach((promise, index) => { + //Reject any elements that have failed to render + if (promise.isPending()) { + timeoutDfd[index].reject(`Error with element ${index}, it failed to render within timeout time`); + } + }); + }); } return retval.promise.then(() => { diff --git a/packages/react-server/core/renderMiddleware.js b/packages/react-server/core/renderMiddleware.js index e1e52e66f..8f5f1c47e 100644 --- a/packages/react-server/core/renderMiddleware.js +++ b/packages/react-server/core/renderMiddleware.js @@ -721,10 +721,11 @@ function writeBody(req, res, context, start, page) { , timeRemaining = totalWait - (new Date - start) var retval = Q.defer(); + var writeBodyDfd = Q.defer(); // If we exceed the timeout then we'll just send empty elements for // anything that hadn't rendered yet. - retval.promise.catch(() => { + writeBodyDfd.promise.catch((err) => { // Write out what we've got. writeElements(res, rendered.map( @@ -736,13 +737,17 @@ function writeBody(req, res, context, start, page) { // Let the client know it's not getting any more data. renderScriptsAsync([{ text: `__reactServerClientController.failArrival()` }], res) + + //Log timeout error but still resolve so we continue in the lifecycle process + logger.error("Error in writeBody", err); + retval.resolve(); }); - Q.all(dfds.map(dfd => dfd.promise)).then(retval.resolve); + Q.all(dfds.map(dfd => dfd.promise)).then(writeBodyDfd.resolve); const timeout = setTimeout(() => { // give some additional information when we time out - retval.reject({ + writeBodyDfd.reject({ message: "Timed out rendering.", // `timeRemaining` is how long we waited before timing out timeWaited: timeRemaining, @@ -759,7 +764,11 @@ function writeBody(req, res, context, start, page) { }, timeRemaining); // Don't leave dead timers hanging around. - retval.promise.then(() => clearTimeout(timeout)); + writeBodyDfd.promise.then(() => { + clearTimeout(timeout); + //writeBody ran successfully, sweet + retval.resolve(); + }); return retval.promise; }