diff --git a/packages/next/client/route-loader.ts b/packages/next/client/route-loader.ts index c761dd0912772..3d826df05813d 100644 --- a/packages/next/client/route-loader.ts +++ b/packages/next/client/route-loader.ts @@ -149,10 +149,29 @@ function appendScript( }) } -function idleTimeout(ms: number, err: Error): Promise { - return new Promise((_resolve, reject) => - requestIdleCallback(() => setTimeout(() => reject(err), ms)) - ) +// Resolve a promise that times out after given amount of milliseconds. +function resolvePromiseWithTimeout( + p: Promise, + ms: number, + err: Error +): Promise { + return new Promise((resolve, reject) => { + let cancelled = false + + p.then((r) => { + // Resolved, cancel the timeout + cancelled = true + resolve(r) + }).catch(reject) + + requestIdleCallback(() => + setTimeout(() => { + if (!cancelled) { + reject(err) + } + }, ms) + ) + }) } // TODO: stop exporting or cache the failure @@ -176,13 +195,12 @@ export function getClientBuildManifest(): Promise { cb && cb() } }) - return Promise.race([ + + return resolvePromiseWithTimeout( onBuildManifest, - idleTimeout( - MS_MAX_IDLE_DELAY, - markAssetError(new Error('Failed to load client build manifest')) - ), - ]) + MS_MAX_IDLE_DELAY, + markAssetError(new Error('Failed to load client build manifest')) + ) } interface RouteFiles { @@ -298,15 +316,14 @@ function createRouteLoader(assetPrefix: string): RouteLoader { Promise.all(css.map(fetchStyleSheet)), ] as const) - const entrypoint: RouteEntrypoint = await Promise.race([ + const entrypoint: RouteEntrypoint = await resolvePromiseWithTimeout( this.whenEntrypoint(route), - idleTimeout( - MS_MAX_IDLE_DELAY, - markAssetError( - new Error(`Route did not complete loading: ${route}`) - ) - ), - ]) + MS_MAX_IDLE_DELAY, + markAssetError( + new Error(`Route did not complete loading: ${route}`) + ) + ) + const res: RouteLoaderEntry = Object.assign< { styles: RouteStyleSheet[] }, RouteEntrypoint