@@ -5,6 +5,8 @@ import * as domain from 'domain';
5
5
import { run as domainTaskRun } from 'domain-task/main' ;
6
6
import { baseUrl } from 'domain-task/fetch' ;
7
7
8
+ const defaultTimeoutMilliseconds = 30 * 1000 ;
9
+
8
10
export interface RenderToStringCallback {
9
11
( error : any , result : RenderToStringResult ) : void ;
10
12
}
@@ -33,7 +35,7 @@ export interface BootModuleInfo {
33
35
webpackConfig ?: string ;
34
36
}
35
37
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 ) {
37
39
findBootFunc ( applicationBasePath , bootModule , ( findBootFuncError , bootFunc ) => {
38
40
if ( findBootFuncError ) {
39
41
callback ( findBootFuncError , null ) ;
@@ -66,8 +68,22 @@ export function renderToString(callback: RenderToStringCallback, applicationBase
66
68
// Make the base URL available to the 'domain-tasks/fetch' helper within this execution context
67
69
baseUrl ( absoluteRequestUrl ) ;
68
70
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
+
69
85
// Actually perform the rendering
70
- bootFunc ( params ) . then ( successResult => {
86
+ bootFuncPromiseWithTimeout . then ( successResult => {
71
87
callback ( null , { html : successResult . html , globals : successResult . globals } ) ;
72
88
} , error => {
73
89
callback ( error , null ) ;
@@ -84,6 +100,25 @@ export function renderToString(callback: RenderToStringCallback, applicationBase
84
100
} ) ;
85
101
}
86
102
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
+
87
122
function findBootModule < T > ( applicationBasePath : string , bootModule : BootModuleInfo , callback : ( error : any , foundModule : T ) => void ) {
88
123
const bootModuleNameFullPath = path . resolve ( applicationBasePath , bootModule . moduleName ) ;
89
124
if ( bootModule . webpackConfig ) {
0 commit comments