@@ -5,6 +5,8 @@ import * as domain from 'domain';
55import { run as domainTaskRun } from 'domain-task/main' ;
66import { baseUrl } from 'domain-task/fetch' ;
77
8+ const defaultTimeoutMilliseconds = 30 * 1000 ;
9+
810export interface RenderToStringCallback {
911 ( error : any , result : RenderToStringResult ) : void ;
1012}
@@ -33,7 +35,7 @@ export interface BootModuleInfo {
3335 webpackConfig ?: string ;
3436}
3537
36- export function renderToString ( callback : RenderToStringCallback , applicationBasePath : string , bootModule : BootModuleInfo , absoluteRequestUrl : string , requestPathAndQuery : string , customDataParameter : any ) {
38+ export function renderToString ( callback : RenderToStringCallback , applicationBasePath : string , bootModule : BootModuleInfo , absoluteRequestUrl : string , requestPathAndQuery : string , customDataParameter : any , overrideTimeoutMilliseconds : number ) {
3739 findBootFunc ( applicationBasePath , bootModule , ( findBootFuncError , bootFunc ) => {
3840 if ( findBootFuncError ) {
3941 callback ( findBootFuncError , null ) ;
@@ -66,8 +68,22 @@ export function renderToString(callback: RenderToStringCallback, applicationBase
6668 // Make the base URL available to the 'domain-tasks/fetch' helper within this execution context
6769 baseUrl ( absoluteRequestUrl ) ;
6870
71+ // Begin rendering, and apply a timeout
72+ const bootFuncPromise = bootFunc ( params ) ;
73+ if ( ! bootFuncPromise || typeof bootFuncPromise . then !== 'function' ) {
74+ callback ( `Prerendering failed because the boot function in ${ bootModule . moduleName } did not return a promise.` , null ) ;
75+ return ;
76+ }
77+ const timeoutMilliseconds = overrideTimeoutMilliseconds || defaultTimeoutMilliseconds ; // e.g., pass -1 to override as 'never time out'
78+ const bootFuncPromiseWithTimeout = timeoutMilliseconds > 0
79+ ? wrapWithTimeout ( bootFuncPromise , timeoutMilliseconds ,
80+ `Prerendering timed out after ${ timeoutMilliseconds } ms because the boot function in '${ bootModule . moduleName } ' `
81+ + 'returned a promise that did not resolve or reject. Make sure that your boot function always resolves or '
82+ + 'rejects its promise. You can change the timeout value using the \'asp-prerender-timeout\' tag helper.' )
83+ : bootFuncPromise ;
84+
6985 // Actually perform the rendering
70- bootFunc ( params ) . then ( successResult => {
86+ bootFuncPromiseWithTimeout . then ( successResult => {
7187 callback ( null , { html : successResult . html , globals : successResult . globals } ) ;
7288 } , error => {
7389 callback ( error , null ) ;
@@ -84,6 +100,25 @@ export function renderToString(callback: RenderToStringCallback, applicationBase
84100 } ) ;
85101}
86102
103+ function wrapWithTimeout < T > ( promise : Promise < T > , timeoutMilliseconds : number , timeoutRejectionValue : any ) : Promise < T > {
104+ return new Promise < T > ( ( resolve , reject ) => {
105+ const timeoutTimer = setTimeout ( ( ) => {
106+ reject ( timeoutRejectionValue ) ;
107+ } , timeoutMilliseconds ) ;
108+
109+ promise . then (
110+ resolvedValue => {
111+ clearTimeout ( timeoutTimer ) ;
112+ resolve ( resolvedValue ) ;
113+ } ,
114+ rejectedValue => {
115+ clearTimeout ( timeoutTimer ) ;
116+ reject ( rejectedValue ) ;
117+ }
118+ )
119+ } ) ;
120+ }
121+
87122function findBootModule < T > ( applicationBasePath : string , bootModule : BootModuleInfo , callback : ( error : any , foundModule : T ) => void ) {
88123 const bootModuleNameFullPath = path . resolve ( applicationBasePath , bootModule . moduleName ) ;
89124 if ( bootModule . webpackConfig ) {
0 commit comments