refactor: Deferred to a class (#10282)

This commit is contained in:
Nikolay Vitkov 2023-05-31 23:36:19 +02:00 committed by GitHub
parent 5fc136eec1
commit 39e9737232
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
23 changed files with 164 additions and 149 deletions

View File

@ -194,6 +194,12 @@ module.exports = {
selector: "CallExpression[callee.name='require']", selector: "CallExpression[callee.name='require']",
message: '`require` statements are not allowed. Use `import`.', message: '`require` statements are not allowed. Use `import`.',
}, },
{
// We need this as NodeJS will run until all the timers have resolved
message: 'Use method `Deferred.race()` instead.',
selector:
'MemberExpression[object.name="Promise"][property.name="race"]',
},
], ],
'@typescript-eslint/no-floating-promises': [ '@typescript-eslint/no-floating-promises': [
'error', 'error',

View File

@ -61,7 +61,6 @@ import {
import type {WebWorker} from '../common/WebWorker.js'; import type {WebWorker} from '../common/WebWorker.js';
import {assert} from '../util/assert.js'; import {assert} from '../util/assert.js';
import {Deferred} from '../util/Deferred.js'; import {Deferred} from '../util/Deferred.js';
import {createDeferred} from '../util/Deferred.js';
import type {Browser} from './Browser.js'; import type {Browser} from './Browser.js';
import type {BrowserContext} from './BrowserContext.js'; import type {BrowserContext} from './BrowserContext.js';
@ -1638,8 +1637,8 @@ export class Page extends EventEmitter {
timeout: number, timeout: number,
closedDeferred: Deferred<TargetCloseError> closedDeferred: Deferred<TargetCloseError>
): Promise<void> { ): Promise<void> {
const idleDeferred = createDeferred<void>(); const idleDeferred = Deferred.create<void>();
const abortDeferred = createDeferred<Error>(); const abortDeferred = Deferred.create<Error>();
let idleTimer: NodeJS.Timeout | undefined; let idleTimer: NodeJS.Timeout | undefined;
const cleanup = () => { const cleanup = () => {
@ -1651,7 +1650,9 @@ export class Page extends EventEmitter {
clearTimeout(idleTimer); clearTimeout(idleTimer);
if (networkManager.inFlightRequestsCount() === 0) { if (networkManager.inFlightRequestsCount() === 0) {
idleTimer = setTimeout(idleDeferred.resolve, idleTime); idleTimer = setTimeout(() => {
return idleDeferred.resolve();
}, idleTime);
} }
}; };
@ -1676,11 +1677,7 @@ export class Page extends EventEmitter {
evaluate(); evaluate();
await Promise.race([ await Deferred.race([idleDeferred, ...eventPromises, closedDeferred]).then(
idleDeferred.valueOrThrow(),
...eventPromises,
closedDeferred.valueOrThrow(),
]).then(
r => { r => {
cleanup(); cleanup();
return r; return r;

View File

@ -33,7 +33,7 @@ import {
import {BrowserContext} from '../api/BrowserContext.js'; import {BrowserContext} from '../api/BrowserContext.js';
import {Page} from '../api/Page.js'; import {Page} from '../api/Page.js';
import {assert} from '../util/assert.js'; import {assert} from '../util/assert.js';
import {createDeferred} from '../util/Deferred.js'; import {Deferred} from '../util/Deferred.js';
import {ChromeTargetManager} from './ChromeTargetManager.js'; import {ChromeTargetManager} from './ChromeTargetManager.js';
import {CDPSession, Connection, ConnectionEmittedEvents} from './Connection.js'; import {CDPSession, Connection, ConnectionEmittedEvents} from './Connection.js';
@ -501,7 +501,7 @@ export class CDPBrowser extends BrowserBase {
options: WaitForTargetOptions = {} options: WaitForTargetOptions = {}
): Promise<Target> { ): Promise<Target> {
const {timeout = 30000} = options; const {timeout = 30000} = options;
const targetDeferred = createDeferred<Target | PromiseLike<Target>>(); const targetDeferred = Deferred.create<Target | PromiseLike<Target>>();
this.on(BrowserEmittedEvents.TargetCreated, check); this.on(BrowserEmittedEvents.TargetCreated, check);
this.on(BrowserEmittedEvents.TargetChanged, check); this.on(BrowserEmittedEvents.TargetChanged, check);

View File

@ -18,7 +18,7 @@ import {Protocol} from 'devtools-protocol';
import {TargetFilterCallback} from '../api/Browser.js'; import {TargetFilterCallback} from '../api/Browser.js';
import {assert} from '../util/assert.js'; import {assert} from '../util/assert.js';
import {createDeferred} from '../util/Deferred.js'; import {Deferred} from '../util/Deferred.js';
import {CDPSession, Connection} from './Connection.js'; import {CDPSession, Connection} from './Connection.js';
import {EventEmitter} from './EventEmitter.js'; import {EventEmitter} from './EventEmitter.js';
@ -81,7 +81,7 @@ export class ChromeTargetManager extends EventEmitter implements TargetManager {
(event: Protocol.Target.DetachedFromTargetEvent) => void (event: Protocol.Target.DetachedFromTargetEvent) => void
> = new WeakMap(); > = new WeakMap();
#initializeDeferred = createDeferred<void>(); #initializeDeferred = Deferred.create<void>();
#targetsIdsForInit: Set<string> = new Set(); #targetsIdsForInit: Set<string> = new Set();
constructor( constructor(

View File

@ -18,7 +18,7 @@ import {Protocol} from 'devtools-protocol';
import {ProtocolMapping} from 'devtools-protocol/types/protocol-mapping.js'; import {ProtocolMapping} from 'devtools-protocol/types/protocol-mapping.js';
import {assert} from '../util/assert.js'; import {assert} from '../util/assert.js';
import {createDeferred, Deferred} from '../util/util.js'; import {Deferred} from '../util/util.js';
import {ConnectionTransport} from './ConnectionTransport.js'; import {ConnectionTransport} from './ConnectionTransport.js';
import {debug} from './Debug.js'; import {debug} from './Debug.js';
@ -64,7 +64,7 @@ function createIncrementalIdGenerator(): GetIdFn {
export class Callback { export class Callback {
#id: number; #id: number;
#error = new ProtocolError(); #error = new ProtocolError();
#deferred = createDeferred<unknown>(); #deferred = Deferred.create<unknown>();
#timer?: ReturnType<typeof setTimeout>; #timer?: ReturnType<typeof setTimeout>;
#label: string; #label: string;

View File

@ -18,7 +18,7 @@ import Protocol from 'devtools-protocol';
import {WaitTimeoutOptions} from '../api/Page.js'; import {WaitTimeoutOptions} from '../api/Page.js';
import {assert} from '../util/assert.js'; import {assert} from '../util/assert.js';
import {createDeferred, Deferred} from '../util/Deferred.js'; import {Deferred} from '../util/Deferred.js';
import {CDPSession} from './Connection.js'; import {CDPSession} from './Connection.js';
import {TimeoutSettings} from './TimeoutSettings.js'; import {TimeoutSettings} from './TimeoutSettings.js';
@ -151,7 +151,7 @@ export class DeviceRequestPrompt {
} }
const {timeout = this.#timeoutSettings.timeout()} = options; const {timeout = this.#timeoutSettings.timeout()} = options;
const deferred = createDeferred<DeviceRequestPromptDevice>({ const deferred = Deferred.create<DeviceRequestPromptDevice>({
message: `Waiting for \`DeviceRequestPromptDevice\` failed: ${timeout}ms exceeded`, message: `Waiting for \`DeviceRequestPromptDevice\` failed: ${timeout}ms exceeded`,
timeout, timeout,
}); });
@ -250,7 +250,7 @@ export class DeviceRequestPromptManager {
} }
const {timeout = this.#timeoutSettings.timeout()} = options; const {timeout = this.#timeoutSettings.timeout()} = options;
const deferred = createDeferred<DeviceRequestPrompt>({ const deferred = Deferred.create<DeviceRequestPrompt>({
message: `Waiting for \`DeviceRequestPrompt\` failed: ${timeout}ms exceeded`, message: `Waiting for \`DeviceRequestPrompt\` failed: ${timeout}ms exceeded`,
timeout, timeout,
}); });

View File

@ -18,7 +18,7 @@ import {Protocol} from 'devtools-protocol';
import {TargetFilterCallback} from '../api/Browser.js'; import {TargetFilterCallback} from '../api/Browser.js';
import {assert} from '../util/assert.js'; import {assert} from '../util/assert.js';
import {createDeferred} from '../util/Deferred.js'; import {Deferred} from '../util/Deferred.js';
import {CDPSession, Connection} from './Connection.js'; import {CDPSession, Connection} from './Connection.js';
import {EventEmitter} from './EventEmitter.js'; import {EventEmitter} from './EventEmitter.js';
@ -88,7 +88,7 @@ export class FirefoxTargetManager
(event: Protocol.Target.AttachedToTargetEvent) => Promise<void> (event: Protocol.Target.AttachedToTargetEvent) => Promise<void>
> = new WeakMap(); > = new WeakMap();
#initializeDeferred = createDeferred<void>(); #initializeDeferred = Deferred.create<void>();
#targetsIdsForInit: Set<string> = new Set(); #targetsIdsForInit: Set<string> = new Set();
constructor( constructor(

View File

@ -26,6 +26,7 @@ import {
import {HTTPResponse} from '../api/HTTPResponse.js'; import {HTTPResponse} from '../api/HTTPResponse.js';
import {Page, WaitTimeoutOptions} from '../api/Page.js'; import {Page, WaitTimeoutOptions} from '../api/Page.js';
import {assert} from '../util/assert.js'; import {assert} from '../util/assert.js';
import {Deferred} from '../util/Deferred.js';
import {isErrorLike} from '../util/ErrorLike.js'; import {isErrorLike} from '../util/ErrorLike.js';
import {CDPSession} from './Connection.js'; import {CDPSession} from './Connection.js';
@ -123,7 +124,7 @@ export class Frame extends BaseFrame {
waitUntil, waitUntil,
timeout timeout
); );
let error = await Promise.race([ let error = await Deferred.race([
navigate( navigate(
this.#client, this.#client,
url, url,
@ -134,7 +135,7 @@ export class Frame extends BaseFrame {
watcher.timeoutOrTerminationPromise(), watcher.timeoutOrTerminationPromise(),
]); ]);
if (!error) { if (!error) {
error = await Promise.race([ error = await Deferred.race([
watcher.timeoutOrTerminationPromise(), watcher.timeoutOrTerminationPromise(),
ensureNewDocumentNavigation ensureNewDocumentNavigation
? watcher.newDocumentNavigationPromise() ? watcher.newDocumentNavigationPromise()
@ -197,7 +198,7 @@ export class Frame extends BaseFrame {
waitUntil, waitUntil,
timeout timeout
); );
const error = await Promise.race([ const error = await Deferred.race([
watcher.timeoutOrTerminationPromise(), watcher.timeoutOrTerminationPromise(),
watcher.sameDocumentNavigationPromise(), watcher.sameDocumentNavigationPromise(),
watcher.newDocumentNavigationPromise(), watcher.newDocumentNavigationPromise(),
@ -389,8 +390,8 @@ export class Frame extends BaseFrame {
return this.worlds[MAIN_WORLD].transferHandle( return this.worlds[MAIN_WORLD].transferHandle(
await this.worlds[PUPPETEER_WORLD].evaluateHandle( await this.worlds[PUPPETEER_WORLD].evaluateHandle(
async ({createDeferred}, {url, id, type, content}) => { async ({Deferred}, {url, id, type, content}) => {
const deferred = createDeferred<void>(); const deferred = Deferred.create<void>();
const script = document.createElement('script'); const script = document.createElement('script');
script.type = type; script.type = type;
script.text = content; script.text = content;
@ -457,8 +458,8 @@ export class Frame extends BaseFrame {
return this.worlds[MAIN_WORLD].transferHandle( return this.worlds[MAIN_WORLD].transferHandle(
await this.worlds[PUPPETEER_WORLD].evaluateHandle( await this.worlds[PUPPETEER_WORLD].evaluateHandle(
async ({createDeferred}, {url, content}) => { async ({Deferred}, {url, content}) => {
const deferred = createDeferred<void>(); const deferred = Deferred.create<void>();
let element: HTMLStyleElement | HTMLLinkElement; let element: HTMLStyleElement | HTMLLinkElement;
if (!url) { if (!url) {
element = document.createElement('style'); element = document.createElement('style');

View File

@ -15,7 +15,7 @@
*/ */
import {Frame as BaseFrame} from '../api/Frame.js'; import {Frame as BaseFrame} from '../api/Frame.js';
import {createDeferred, Deferred} from '../util/Deferred.js'; import {Deferred} from '../util/Deferred.js';
/** /**
* Keeps track of the page frame tree and it's is managed by * Keeps track of the page frame tree and it's is managed by
@ -50,7 +50,7 @@ export class FrameTree<Frame extends BaseFrame> {
if (frame) { if (frame) {
return Promise.resolve(frame); return Promise.resolve(frame);
} }
const deferred = createDeferred<Frame>(); const deferred = Deferred.create<Frame>();
const callbacks = const callbacks =
this.#waitRequests.get(frameId) || new Set<Deferred<Frame>>(); this.#waitRequests.get(frameId) || new Set<Deferred<Frame>>();
callbacks.add(deferred); callbacks.add(deferred);

View File

@ -20,7 +20,7 @@ import {
HTTPResponse as BaseHTTPResponse, HTTPResponse as BaseHTTPResponse,
RemoteAddress, RemoteAddress,
} from '../api/HTTPResponse.js'; } from '../api/HTTPResponse.js';
import {createDeferred} from '../util/Deferred.js'; import {Deferred} from '../util/Deferred.js';
import {CDPSession} from './Connection.js'; import {CDPSession} from './Connection.js';
import {ProtocolError} from './Errors.js'; import {ProtocolError} from './Errors.js';
@ -34,7 +34,7 @@ export class HTTPResponse extends BaseHTTPResponse {
#client: CDPSession; #client: CDPSession;
#request: HTTPRequest; #request: HTTPRequest;
#contentPromise: Promise<Buffer> | null = null; #contentPromise: Promise<Buffer> | null = null;
#bodyLoadedDeferred = createDeferred<Error | void>(); #bodyLoadedDeferred = Deferred.create<Error | void>();
#remoteAddress: RemoteAddress; #remoteAddress: RemoteAddress;
#status: number; #status: number;
#statusText: string; #statusText: string;

View File

@ -19,7 +19,7 @@ import {Protocol} from 'devtools-protocol';
import type {ClickOptions, ElementHandle} from '../api/ElementHandle.js'; import type {ClickOptions, ElementHandle} from '../api/ElementHandle.js';
import {JSHandle} from '../api/JSHandle.js'; import {JSHandle} from '../api/JSHandle.js';
import {assert} from '../util/assert.js'; import {assert} from '../util/assert.js';
import {createDeferred} from '../util/Deferred.js'; import {Deferred} from '../util/Deferred.js';
import {Binding} from './Binding.js'; import {Binding} from './Binding.js';
import {CDPSession} from './Connection.js'; import {CDPSession} from './Connection.js';
@ -101,7 +101,7 @@ export interface IsolatedWorldChart {
export class IsolatedWorld { export class IsolatedWorld {
#frame: Frame; #frame: Frame;
#document?: ElementHandle<Document>; #document?: ElementHandle<Document>;
#context = createDeferred<ExecutionContext>(); #context = Deferred.create<ExecutionContext>();
#detached = false; #detached = false;
// Set of bindings that have been registered in the current context. // Set of bindings that have been registered in the current context.
@ -144,7 +144,7 @@ export class IsolatedWorld {
clearContext(): void { clearContext(): void {
this.#document = undefined; this.#document = undefined;
this.#context = createDeferred(); this.#context = Deferred.create();
} }
setContext(context: ExecutionContext): void { setContext(context: ExecutionContext): void {
@ -304,7 +304,7 @@ export class IsolatedWorld {
waitUntil, waitUntil,
timeout timeout
); );
const error = await Promise.race([ const error = await Deferred.race<void | Error | undefined>([
watcher.timeoutOrTerminationPromise(), watcher.timeoutOrTerminationPromise(),
watcher.lifecyclePromise(), watcher.lifecyclePromise(),
]); ]);

View File

@ -16,7 +16,7 @@
import {HTTPResponse} from '../api/HTTPResponse.js'; import {HTTPResponse} from '../api/HTTPResponse.js';
import {assert} from '../util/assert.js'; import {assert} from '../util/assert.js';
import {Deferred, createDeferred} from '../util/Deferred.js'; import {Deferred} from '../util/Deferred.js';
import {CDPSessionEmittedEvents} from './Connection.js'; import {CDPSessionEmittedEvents} from './Connection.js';
import {TimeoutError} from './Errors.js'; import {TimeoutError} from './Errors.js';
@ -71,10 +71,10 @@ export class LifecycleWatcher {
#eventListeners: PuppeteerEventListener[]; #eventListeners: PuppeteerEventListener[];
#initialLoaderId: string; #initialLoaderId: string;
#sameDocumentNavigationDeferred = createDeferred<Error | undefined>(); #sameDocumentNavigationDeferred = Deferred.create<undefined>();
#lifecycleDeferred = createDeferred<void>(); #lifecycleDeferred = Deferred.create<void>();
#newDocumentNavigationDeferred = createDeferred<Error | undefined>(); #newDocumentNavigationDeferred = Deferred.create<undefined>();
#terminationDeferred = createDeferred<Error | undefined>(); #terminationDeferred = Deferred.create<Error>();
#timeoutPromise: Promise<TimeoutError | undefined>; #timeoutPromise: Promise<TimeoutError | undefined>;
@ -169,7 +169,7 @@ export class LifecycleWatcher {
// navigation requests reported by the backend. This generally should not // navigation requests reported by the backend. This generally should not
// happen by it looks like it's possible. // happen by it looks like it's possible.
this.#navigationResponseReceived?.resolve(); this.#navigationResponseReceived?.resolve();
this.#navigationResponseReceived = createDeferred(); this.#navigationResponseReceived = Deferred.create();
if (request.response() !== null) { if (request.response() !== null) {
this.#navigationResponseReceived?.resolve(); this.#navigationResponseReceived?.resolve();
} }
@ -222,10 +222,7 @@ export class LifecycleWatcher {
} }
timeoutOrTerminationPromise(): Promise<Error | TimeoutError | undefined> { timeoutOrTerminationPromise(): Promise<Error | TimeoutError | undefined> {
return Promise.race([ return Deferred.race([this.#timeoutPromise, this.#terminationDeferred]);
this.#timeoutPromise,
this.#terminationDeferred.valueOrThrow(),
]);
} }
async #createTimeoutPromise(): Promise<TimeoutError | undefined> { async #createTimeoutPromise(): Promise<TimeoutError | undefined> {
@ -299,6 +296,6 @@ export class LifecycleWatcher {
dispose(): void { dispose(): void {
removeEventListeners(this.#eventListeners); removeEventListeners(this.#eventListeners);
this.#maximumTimer !== undefined && clearTimeout(this.#maximumTimer); clearTimeout(this.#maximumTimer);
} }
} }

View File

@ -43,7 +43,7 @@ import {
NewDocumentScriptEvaluation, NewDocumentScriptEvaluation,
} from '../api/Page.js'; } from '../api/Page.js';
import {assert} from '../util/assert.js'; import {assert} from '../util/assert.js';
import {createDeferred, Deferred} from '../util/Deferred.js'; import {Deferred} from '../util/Deferred.js';
import {isErrorLike} from '../util/ErrorLike.js'; import {isErrorLike} from '../util/ErrorLike.js';
import {Accessibility} from './Accessibility.js'; import {Accessibility} from './Accessibility.js';
@ -153,7 +153,7 @@ export class CDPPage extends Page {
#screenshotTaskQueue: TaskQueue; #screenshotTaskQueue: TaskQueue;
#workers = new Map<string, WebWorker>(); #workers = new Map<string, WebWorker>();
#fileChooserDeferreds = new Set<Deferred<FileChooser>>(); #fileChooserDeferreds = new Set<Deferred<FileChooser>>();
#sessionCloseDeferred = createDeferred<TargetCloseError>(); #sessionCloseDeferred = Deferred.create<TargetCloseError>();
#serviceWorkerBypassed = false; #serviceWorkerBypassed = false;
#userDragInterceptionEnabled = false; #userDragInterceptionEnabled = false;
@ -374,7 +374,7 @@ export class CDPPage extends Page {
): Promise<FileChooser> { ): Promise<FileChooser> {
const needsEnable = this.#fileChooserDeferreds.size === 0; const needsEnable = this.#fileChooserDeferreds.size === 0;
const {timeout = this.#timeoutSettings.timeout()} = options; const {timeout = this.#timeoutSettings.timeout()} = options;
const deferred = createDeferred<FileChooser>({ const deferred = Deferred.create<FileChooser>({
message: `Waiting for \`FileChooser\` failed: ${timeout}ms exceeded`, message: `Waiting for \`FileChooser\` failed: ${timeout}ms exceeded`,
timeout, timeout,
}); });
@ -1029,7 +1029,7 @@ export class CDPPage extends Page {
}; };
} }
const eventRace: Promise<Frame> = Promise.race([ const eventRace: Promise<Frame> = Deferred.race([
waitForEvent( waitForEvent(
this.#frameManager, this.#frameManager,
FrameManagerEmittedEvents.FrameAttached, FrameManagerEmittedEvents.FrameAttached,

View File

@ -19,7 +19,7 @@ import {Protocol} from 'devtools-protocol';
import type {Browser} from '../api/Browser.js'; import type {Browser} from '../api/Browser.js';
import type {BrowserContext} from '../api/BrowserContext.js'; import type {BrowserContext} from '../api/BrowserContext.js';
import {Page, PageEmittedEvents} from '../api/Page.js'; import {Page, PageEmittedEvents} from '../api/Page.js';
import {createDeferred} from '../util/Deferred.js'; import {Deferred} from '../util/Deferred.js';
import {CDPSession} from './Connection.js'; import {CDPSession} from './Connection.js';
import {CDPPage} from './Page.js'; import {CDPPage} from './Page.js';
@ -55,11 +55,11 @@ export class Target {
/** /**
* @internal * @internal
*/ */
_initializedDeferred = createDeferred<InitializationStatus>(); _initializedDeferred = Deferred.create<InitializationStatus>();
/** /**
* @internal * @internal
*/ */
_isClosedDeferred = createDeferred<void>(); _isClosedDeferred = Deferred.create<void>();
/** /**
* @internal * @internal
*/ */

View File

@ -17,7 +17,7 @@
import {ElementHandle} from '../api/ElementHandle.js'; import {ElementHandle} from '../api/ElementHandle.js';
import {JSHandle} from '../api/JSHandle.js'; import {JSHandle} from '../api/JSHandle.js';
import type {Poller} from '../injected/Poller.js'; import type {Poller} from '../injected/Poller.js';
import {createDeferred} from '../util/Deferred.js'; import {Deferred} from '../util/Deferred.js';
import {isErrorLike} from '../util/ErrorLike.js'; import {isErrorLike} from '../util/ErrorLike.js';
import {stringifyFunction} from '../util/Function.js'; import {stringifyFunction} from '../util/Function.js';
@ -49,7 +49,7 @@ export class WaitTask<T = unknown> {
#timeout?: NodeJS.Timeout; #timeout?: NodeJS.Timeout;
#result = createDeferred<HandleFor<T>>(); #result = Deferred.create<HandleFor<T>>();
#poller?: JSHandle<Poller<T>>; #poller?: JSHandle<Poller<T>>;
#signal?: AbortSignal; #signal?: AbortSignal;

View File

@ -15,7 +15,7 @@
*/ */
import {Protocol} from 'devtools-protocol'; import {Protocol} from 'devtools-protocol';
import {createDeferred} from '../util/Deferred.js'; import {Deferred} from '../util/Deferred.js';
import {CDPSession} from './Connection.js'; import {CDPSession} from './Connection.js';
import {ConsoleMessageType} from './ConsoleMessage.js'; import {ConsoleMessageType} from './ConsoleMessage.js';
@ -68,7 +68,7 @@ export type ExceptionThrownCallback = (
* @public * @public
*/ */
export class WebWorker extends EventEmitter { export class WebWorker extends EventEmitter {
#executionContext = createDeferred<ExecutionContext>(); #executionContext = Deferred.create<ExecutionContext>();
#client: CDPSession; #client: CDPSession;
#url: string; #url: string;

View File

@ -25,7 +25,7 @@ import {
WaitForOptions, WaitForOptions,
} from '../../api/Page.js'; } from '../../api/Page.js';
import {assert} from '../../util/assert.js'; import {assert} from '../../util/assert.js';
import {createDeferred} from '../../util/Deferred.js'; import {Deferred} from '../../util/Deferred.js';
import {ConsoleMessage, ConsoleMessageLocation} from '../ConsoleMessage.js'; import {ConsoleMessage, ConsoleMessageLocation} from '../ConsoleMessage.js';
import {TargetCloseError} from '../Errors.js'; import {TargetCloseError} from '../Errors.js';
import {Handler} from '../EventEmitter.js'; import {Handler} from '../EventEmitter.js';
@ -61,7 +61,7 @@ export class Page extends PageBase {
#frameTree = new FrameTree<Frame>(); #frameTree = new FrameTree<Frame>();
#networkManager: NetworkManager; #networkManager: NetworkManager;
#viewport: Viewport | null = null; #viewport: Viewport | null = null;
#closedDeferred = createDeferred<TargetCloseError>(); #closedDeferred = Deferred.create<TargetCloseError>();
#subscribedEvents = new Map<string, Handler<any>>([ #subscribedEvents = new Map<string, Handler<any>>([
['log.entryAdded', this.#onLogEntryAdded.bind(this)], ['log.entryAdded', this.#onLogEntryAdded.bind(this)],
[ [

View File

@ -22,8 +22,8 @@ import type {ElementHandle} from '../api/ElementHandle.js';
import type {JSHandle} from '../api/JSHandle.js'; import type {JSHandle} from '../api/JSHandle.js';
import {Page} from '../api/Page.js'; import {Page} from '../api/Page.js';
import {isNode} from '../environment.js'; import {isNode} from '../environment.js';
import {Deferred} from '../puppeteer-core.js';
import {assert} from '../util/assert.js'; import {assert} from '../util/assert.js';
import {createDeferred} from '../util/Deferred.js';
import {isErrorLike} from '../util/ErrorLike.js'; import {isErrorLike} from '../util/ErrorLike.js';
import type {CDPSession} from './Connection.js'; import type {CDPSession} from './Connection.js';
@ -382,7 +382,7 @@ export async function waitForEvent<T>(
timeout: number, timeout: number,
abortPromise: Promise<Error> abortPromise: Promise<Error>
): Promise<T> { ): Promise<T> {
const deferred = createDeferred<T>({ const deferred = Deferred.create<T>({
message: `Timeout exceeded while waiting for event ${String(eventName)}`, message: `Timeout exceeded while waiting for event ${String(eventName)}`,
timeout, timeout,
}); });
@ -391,23 +391,19 @@ export async function waitForEvent<T>(
deferred.resolve(event); deferred.resolve(event);
} }
}); });
return Promise.race([deferred.valueOrThrow(), abortPromise]) return Deferred.race<T | Error>([deferred, abortPromise]).then(
.then( r => {
r => { removeEventListeners([listener]);
removeEventListeners([listener]); if (isErrorLike(r)) {
if (isErrorLike(r)) { throw r;
throw r;
}
return r;
},
error => {
removeEventListeners([listener]);
throw error;
} }
) return r;
.finally(() => { },
deferred.reject(new Error('Cleared')); error => {
}); removeEventListeners([listener]);
throw error;
}
);
} }
/** /**
@ -509,12 +505,12 @@ export async function waitWithTimeout<T>(
taskName: string, taskName: string,
timeout: number timeout: number
): Promise<T> { ): Promise<T> {
const deferred = createDeferred<never>({ const deferred = Deferred.create<never>({
message: `waiting for ${taskName} failed: timeout ${timeout}ms exceeded`, message: `waiting for ${taskName} failed: timeout ${timeout}ms exceeded`,
timeout, timeout,
}); });
return await Promise.race([promise, deferred.valueOrThrow()]).finally(() => { return await Deferred.race([promise, deferred.valueOrThrow()]).finally(() => {
deferred.reject(new Error('Cleared')); deferred.reject(new Error('Cleared'));
}); });
} }

View File

@ -15,7 +15,7 @@
*/ */
import {assert} from '../util/assert.js'; import {assert} from '../util/assert.js';
import {createDeferred, Deferred} from '../util/Deferred.js'; import {Deferred} from '../util/Deferred.js';
/** /**
* @internal * @internal
@ -42,7 +42,7 @@ export class MutationPoller<T> implements Poller<T> {
} }
async start(): Promise<void> { async start(): Promise<void> {
const deferred = (this.#deferred = createDeferred<T>()); const deferred = (this.#deferred = Deferred.create<T>());
const result = await this.#fn(); const result = await this.#fn();
if (result) { if (result) {
deferred.resolve(result); deferred.resolve(result);
@ -92,7 +92,7 @@ export class RAFPoller<T> implements Poller<T> {
} }
async start(): Promise<void> { async start(): Promise<void> {
const deferred = (this.#deferred = createDeferred<T>()); const deferred = (this.#deferred = Deferred.create<T>());
const result = await this.#fn(); const result = await this.#fn();
if (result) { if (result) {
deferred.resolve(result); deferred.resolve(result);
@ -143,7 +143,7 @@ export class IntervalPoller<T> implements Poller<T> {
} }
async start(): Promise<void> { async start(): Promise<void> {
const deferred = (this.#deferred = createDeferred<T>()); const deferred = (this.#deferred = Deferred.create<T>());
const result = await this.#fn(); const result = await this.#fn();
if (result) { if (result) {
deferred.resolve(result); deferred.resolve(result);

View File

@ -14,7 +14,7 @@
* limitations under the License. * limitations under the License.
*/ */
import {createDeferred} from '../util/Deferred.js'; import {Deferred} from '../util/Deferred.js';
import {createFunction} from '../util/Function.js'; import {createFunction} from '../util/Function.js';
import * as ARIAQuerySelector from './ARIAQuerySelector.js'; import * as ARIAQuerySelector from './ARIAQuerySelector.js';
@ -41,7 +41,7 @@ const PuppeteerUtil = Object.freeze({
...TextQuerySelector, ...TextQuerySelector,
...util, ...util,
...XPathQuerySelector, ...XPathQuerySelector,
createDeferred, Deferred,
createFunction, createFunction,
createTextContent, createTextContent,
IntervalPoller, IntervalPoller,

View File

@ -1,6 +1,6 @@
import {DEFERRED_PROMISE_DEBUG_TIMEOUT} from '../environment.js'; import {DEFERRED_PROMISE_DEBUG_TIMEOUT} from '../environment.js';
import {Deferred, createDeferred} from './Deferred.js'; import {Deferred} from './Deferred.js';
/** /**
* Creates and returns a deferred promise using DEFERRED_PROMISE_DEBUG_TIMEOUT * Creates and returns a deferred promise using DEFERRED_PROMISE_DEBUG_TIMEOUT
@ -10,10 +10,10 @@ import {Deferred, createDeferred} from './Deferred.js';
*/ */
export function createDebuggableDeferred<T>(message: string): Deferred<T> { export function createDebuggableDeferred<T>(message: string): Deferred<T> {
if (DEFERRED_PROMISE_DEBUG_TIMEOUT > 0) { if (DEFERRED_PROMISE_DEBUG_TIMEOUT > 0) {
return createDeferred({ return Deferred.create({
message, message,
timeout: DEFERRED_PROMISE_DEBUG_TIMEOUT, timeout: DEFERRED_PROMISE_DEBUG_TIMEOUT,
}); });
} }
return createDeferred(); return Deferred.create();
} }

View File

@ -1,17 +1,5 @@
import {TimeoutError} from '../common/Errors.js'; import {TimeoutError} from '../common/Errors.js';
/**
* @internal
*/
export interface Deferred<T> {
finished: () => boolean;
resolved: () => boolean;
resolve: (value: T) => void;
reject: (error: Error) => void;
value: () => T | Error | undefined;
valueOrThrow: () => Promise<T>;
}
/** /**
* @internal * @internal
*/ */
@ -29,61 +17,94 @@ export interface DeferredOptions {
* *
* @internal * @internal
*/ */
export function createDeferred<T>(opts?: DeferredOptions): Deferred<T> { export class Deferred<T> {
let isResolved = false; #isResolved = false;
let isRejected = false; #isRejected = false;
let _value: T | Error | undefined; #value: T | Error | undefined;
let resolver: (value: void) => void; #resolver: (value: void) => void = () => {};
const taskPromise = new Promise<void>(resolve => { #taskPromise = new Promise<void>(resolve => {
resolver = resolve; this.#resolver = resolve;
}); });
const timeoutId = #timeoutId: ReturnType<typeof setTimeout> | undefined;
opts && opts.timeout > 0
? setTimeout(() => {
reject(new TimeoutError(opts.message));
}, opts.timeout)
: undefined;
function finish(value: T | Error) { constructor(opts?: DeferredOptions) {
clearTimeout(timeoutId); this.#timeoutId =
_value = value; opts && opts.timeout > 0
resolver(); ? setTimeout(() => {
this.reject(new TimeoutError(opts.message));
}, opts.timeout)
: undefined;
} }
function resolve(value: T) { #finish(value: T | Error) {
if (isRejected || isResolved) { clearTimeout(this.#timeoutId);
this.#value = value;
this.#resolver();
}
resolve(value: T): void {
if (this.#isRejected || this.#isResolved) {
return; return;
} }
isResolved = true; this.#isResolved = true;
finish(value); this.#finish(value);
} }
function reject(error: Error) { reject(error: Error): void {
if (isRejected || isResolved) { if (this.#isRejected || this.#isResolved) {
return; return;
} }
isRejected = true; this.#isRejected = true;
finish(error); this.#finish(error);
} }
return { resolved(): boolean {
resolved: () => { return this.#isResolved;
return isResolved; }
},
finished: () => { finished(): boolean {
return isResolved || isRejected; return this.#isResolved || this.#isRejected;
}, }
resolve,
reject, value(): T | Error | undefined {
value: () => { return this.#value;
return _value; }
},
async valueOrThrow() { async valueOrThrow(): Promise<T> {
await taskPromise; await this.#taskPromise;
if (isRejected) { if (this.#isRejected) {
throw _value; throw this.#value;
}
return this.#value as T;
}
static create<R>(opts?: DeferredOptions): Deferred<R> {
return new Deferred<R>(opts);
}
static async race<R>(
awaitables: Array<Promise<R> | Deferred<R>>
): Promise<R> {
const deferredWithTimeout: Set<Deferred<R>> = new Set();
try {
const promises = awaitables.map(value => {
if (value instanceof Deferred) {
deferredWithTimeout.add(value);
return value.valueOrThrow();
}
return value;
});
// eslint-disable-next-line no-restricted-syntax
return await Promise.race(promises);
} finally {
for (const deferred of deferredWithTimeout) {
// We need to stop the timeout else
// Node.JS will keep running the event loop till the
// timer executes
deferred.reject(new Error('Timeout cleared'));
} }
return _value as T; }
}, }
};
} }

View File

@ -15,10 +15,7 @@
*/ */
import expect from 'expect'; import expect from 'expect';
import { import {Deferred} from 'puppeteer-core/internal/util/Deferred.js';
Deferred,
createDeferred,
} from 'puppeteer-core/internal/util/Deferred.js';
describe('DeferredPromise', function () { describe('DeferredPromise', function () {
it('should catch errors', async () => { it('should catch errors', async () => {
@ -30,7 +27,7 @@ describe('DeferredPromise', function () {
} }
// Async function that fails. // Async function that fails.
function fails(): Deferred<void> { function fails(): Deferred<void> {
const deferred = createDeferred<void>(); const deferred = Deferred.create<void>();
setTimeout(() => { setTimeout(() => {
deferred.reject(new Error('test')); deferred.reject(new Error('test'));
}, 25); }, 25);