From adb06d8d8064af88eaf02f44c04c885d55830adc Mon Sep 17 00:00:00 2001 From: Wyatt Johnson Date: Tue, 17 Oct 2023 13:05:31 -0600 Subject: [PATCH] refactor: replaced `Promise.withResolvers` polyfill with internal `DetachedPromise` --- packages/next/src/build/utils.ts | 1 - packages/next/src/export/worker.ts | 1 - packages/next/src/lib/batcher.ts | 7 ++--- packages/next/src/lib/detached-promise.ts | 27 ++++++++++++++++ .../lib/polyfill-promise-with-resolvers.ts | 31 ------------------- .../next/src/server/dev/next-dev-server.ts | 3 +- .../src/server/dev/static-paths-worker.ts | 1 - .../default-route-matcher-manager.ts | 7 ++--- packages/next/src/server/lib/router-server.ts | 1 - packages/next/src/server/next-server.ts | 1 - .../next/src/server/response-cache/web.ts | 3 +- packages/next/types/index.d.ts | 15 --------- 12 files changed, 35 insertions(+), 63 deletions(-) create mode 100644 packages/next/src/lib/detached-promise.ts delete mode 100644 packages/next/src/lib/polyfill-promise-with-resolvers.ts diff --git a/packages/next/src/build/utils.ts b/packages/next/src/build/utils.ts index beb14976125d3..30e0949aa2918 100644 --- a/packages/next/src/build/utils.ts +++ b/packages/next/src/build/utils.ts @@ -21,7 +21,6 @@ import '../server/require-hook' import '../server/node-polyfill-fetch' import '../server/node-polyfill-crypto' import '../server/node-environment' -import '../lib/polyfill-promise-with-resolvers' import { green, yellow, red, cyan, bold, underline } from '../lib/picocolors' import getGzipSize from 'next/dist/compiled/gzip-size' diff --git a/packages/next/src/export/worker.ts b/packages/next/src/export/worker.ts index a3ee08796c1fd..de3d1af82b84a 100644 --- a/packages/next/src/export/worker.ts +++ b/packages/next/src/export/worker.ts @@ -11,7 +11,6 @@ import type { import '../server/node-polyfill-fetch' import '../server/node-polyfill-web-streams' import '../server/node-environment' -import '../lib/polyfill-promise-with-resolvers' process.env.NEXT_IS_EXPORT_WORKER = 'true' diff --git a/packages/next/src/lib/batcher.ts b/packages/next/src/lib/batcher.ts index 66a69be757c4a..eca1df6dd4ab2 100644 --- a/packages/next/src/lib/batcher.ts +++ b/packages/next/src/lib/batcher.ts @@ -1,8 +1,5 @@ -// This takes advantage of `Promise.withResolvers` which is polyfilled in -// this imported module. -import './polyfill-promise-with-resolvers' - import type { SchedulerFn } from '../server/lib/schedule-on-next-tick' +import { DetachedPromise } from './detached-promise' type CacheKeyFn = ( key: K @@ -72,7 +69,7 @@ export class Batcher { const pending = this.pending.get(cacheKey) if (pending) return pending - const { promise, resolve, reject } = Promise.withResolvers() + const { promise, resolve, reject } = new DetachedPromise() this.pending.set(cacheKey, promise) this.schedulerFn(async () => { diff --git a/packages/next/src/lib/detached-promise.ts b/packages/next/src/lib/detached-promise.ts new file mode 100644 index 0000000000000..d3dac25a5a805 --- /dev/null +++ b/packages/next/src/lib/detached-promise.ts @@ -0,0 +1,27 @@ +/** + * A `Promise.withResolvers` implementation that exposes the `resolve` and + * `reject` functions on a `Promise`. + * + * @see https://tc39.es/proposal-promise-with-resolvers/ + */ +export class DetachedPromise { + public readonly resolve: (value: T | PromiseLike) => void + public readonly reject: (reason: any) => void + public readonly promise: Promise + + constructor() { + let resolve: (value: T | PromiseLike) => void + let reject: (reason: any) => void + + // Create the promise and assign the resolvers to the object. + this.promise = new Promise((res, rej) => { + resolve = res + reject = rej + }) + + // We know that resolvers is defined because the Promise constructor runs + // synchronously. + this.resolve = resolve! + this.reject = reject! + } +} diff --git a/packages/next/src/lib/polyfill-promise-with-resolvers.ts b/packages/next/src/lib/polyfill-promise-with-resolvers.ts deleted file mode 100644 index 7400621f62934..0000000000000 --- a/packages/next/src/lib/polyfill-promise-with-resolvers.ts +++ /dev/null @@ -1,31 +0,0 @@ -// This adds a `Promise.withResolvers` polyfill. This will soon be adopted into -// the spec. -// -// TODO: remove this polyfill when it is adopted into the spec. -// -// https://tc39.es/proposal-promise-with-resolvers/ -// -if ( - !('withResolvers' in Promise) || - typeof Promise.withResolvers !== 'function' -) { - Promise.withResolvers = (): { - readonly promise: Promise - readonly resolve: (value: T | PromiseLike) => void - readonly reject: (reason: any) => void - } => { - let resolvers: { - resolve: (value: T | PromiseLike) => void - reject: (reason: any) => void - } - - // Create the promise and assign the resolvers to the object. - const promise = new Promise((resolve, reject) => { - resolvers = { resolve, reject } - }) - - // We know that resolvers is defined because the Promise constructor runs - // synchronously. - return { promise, resolve: resolvers!.resolve, reject: resolvers!.reject } - } -} diff --git a/packages/next/src/server/dev/next-dev-server.ts b/packages/next/src/server/dev/next-dev-server.ts index 213d2fd934ccf..2e2a03822f00a 100644 --- a/packages/next/src/server/dev/next-dev-server.ts +++ b/packages/next/src/server/dev/next-dev-server.ts @@ -61,6 +61,7 @@ import { DefaultFileReader } from '../future/route-matcher-providers/dev/helpers import { NextBuildContext } from '../../build/build-context' import LRUCache from 'next/dist/compiled/lru-cache' import { getMiddlewareRouteMatcher } from '../../shared/lib/router/utils/middleware-route-matcher' +import { DetachedPromise } from '../../lib/detached-promise' // Load ReactDevOverlay only when needed let ReactDevOverlayImpl: FunctionComponent @@ -89,7 +90,7 @@ export default class DevServer extends Server { * The promise that resolves when the server is ready. When this is unset * the server is ready. */ - private ready? = Promise.withResolvers() + private ready? = new DetachedPromise() protected sortedRoutes?: string[] private pagesDir?: string private appDir?: string diff --git a/packages/next/src/server/dev/static-paths-worker.ts b/packages/next/src/server/dev/static-paths-worker.ts index 99e287b24d0db..2f77e47185e79 100644 --- a/packages/next/src/server/dev/static-paths-worker.ts +++ b/packages/next/src/server/dev/static-paths-worker.ts @@ -3,7 +3,6 @@ import type { NextConfigComplete } from '../config-shared' import '../require-hook' import '../node-polyfill-fetch' import '../node-environment' -import '../../lib/polyfill-promise-with-resolvers' import { buildAppStaticPaths, diff --git a/packages/next/src/server/future/route-matcher-managers/default-route-matcher-manager.ts b/packages/next/src/server/future/route-matcher-managers/default-route-matcher-manager.ts index fdae0fe3aa302..55d274eb4cf73 100644 --- a/packages/next/src/server/future/route-matcher-managers/default-route-matcher-manager.ts +++ b/packages/next/src/server/future/route-matcher-managers/default-route-matcher-manager.ts @@ -1,7 +1,3 @@ -// This takes advantage of `Promise.withResolvers` which is polyfilled in -// this imported module. -import '../../../lib/polyfill-promise-with-resolvers' - import { isDynamicRoute } from '../../../shared/lib/router/utils' import type { RouteKind } from '../route-kind' import type { RouteMatch } from '../route-matches/route-match' @@ -12,6 +8,7 @@ import type { MatchOptions, RouteMatcherManager } from './route-matcher-manager' import { getSortedRoutes } from '../../../shared/lib/router/utils' import { LocaleRouteMatcher } from '../route-matchers/locale-route-matcher' import { ensureLeadingSlash } from '../../../shared/lib/page-path/ensure-leading-slash' +import { DetachedPromise } from '../../../lib/detached-promise' interface RouteMatchers { static: ReadonlyArray @@ -46,7 +43,7 @@ export class DefaultRouteMatcherManager implements RouteMatcherManager { private previousMatchers: ReadonlyArray = [] public async reload() { - const { promise, resolve, reject } = Promise.withResolvers() + const { promise, resolve, reject } = new DetachedPromise() this.waitTillReadyPromise = promise // Grab the compilation ID for this run, we'll verify it at the end to diff --git a/packages/next/src/server/lib/router-server.ts b/packages/next/src/server/lib/router-server.ts index c725f6d70141e..a1da571ba9957 100644 --- a/packages/next/src/server/lib/router-server.ts +++ b/packages/next/src/server/lib/router-server.ts @@ -6,7 +6,6 @@ import type { NextUrlWithParsedQuery } from '../request-meta' import '../node-polyfill-fetch' import '../node-environment' import '../require-hook' -import '../../lib/polyfill-promise-with-resolvers' import url from 'url' import path from 'path' diff --git a/packages/next/src/server/next-server.ts b/packages/next/src/server/next-server.ts index 73817ff2e7483..e88a142b59db4 100644 --- a/packages/next/src/server/next-server.ts +++ b/packages/next/src/server/next-server.ts @@ -4,7 +4,6 @@ import './node-polyfill-fetch' import './node-polyfill-form' import './node-polyfill-web-streams' import './node-polyfill-crypto' -import '../lib/polyfill-promise-with-resolvers' import type { TLSSocket } from 'tls' import type { CacheFs } from '../shared/lib/utils' diff --git a/packages/next/src/server/response-cache/web.ts b/packages/next/src/server/response-cache/web.ts index bab180e6a8a98..118c4ffa9a899 100644 --- a/packages/next/src/server/response-cache/web.ts +++ b/packages/next/src/server/response-cache/web.ts @@ -1,3 +1,4 @@ +import { DetachedPromise } from '../../lib/detached-promise' import type { ResponseCacheEntry, ResponseGenerator } from './types' /** @@ -45,7 +46,7 @@ export default class WebResponseCache { promise, resolve: resolver, reject: rejecter, - } = Promise.withResolvers() + } = new DetachedPromise() if (pendingResponseKey) { this.pendingResponses.set(pendingResponseKey, promise) } diff --git a/packages/next/types/index.d.ts b/packages/next/types/index.d.ts index c1f2196f8d0d6..41e7bc1a580af 100644 --- a/packages/next/types/index.d.ts +++ b/packages/next/types/index.d.ts @@ -318,21 +318,6 @@ declare global { randomUUID(): string } - // TODO: remove this polyfill when it is adopted into the spec. - interface PromiseConstructor { - /** - * Creates a new promise with exposed resolvers to resolve/reject. This will - * be adopted into the spec as `Promise.withResolvers`. - * - * @see https://tc39.es/proposal-promise-with-resolvers/ - */ - withResolvers(): { - promise: Promise - resolve: (value: T | PromiseLike) => void - reject: (reason?: unknown) => void - } - } - var __NEXT_HTTP_AGENT_OPTIONS: { keepAlive?: boolean } | undefined var __NEXT_UNDICI_AGENT_SET: boolean var __NEXT_HTTP_AGENT: HttpAgent