diff --git a/packages/puppeteer-core/src/common/Operation.ts b/packages/puppeteer-core/src/common/Operation.ts deleted file mode 100644 index 3314641471f..00000000000 --- a/packages/puppeteer-core/src/common/Operation.ts +++ /dev/null @@ -1,153 +0,0 @@ -/** - * Copyright 2023 Google Inc. All rights reserved. - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -import type {Awaitable} from './types.js'; - -/** - * Operations are promises that can have effects - * added on them (through {@link Operation.effect}). - * - * Semantically-speaking, adding an effect equates to guaranteeing the operation - * causes the added effect. - * - * The first effect that errors will propogate its error back to the operation. - * - * @example - * - * ```ts - * await input.click().effect(async () => { - * await page.waitForNavigation(); - * }); - * ``` - * - * @remarks - * - * Adding effects to a completed operation will result in an error. This occurs - * when either - * - * 1. the effects are added asynchronously or - * 2. the operation was awaited before effects were added. - * - * For example for (1), - * - * ```ts - * const operation = input.click(); - * await new Promise(resolve => setTimeout(resolve, 100)); - * await operation.effect(() => console.log('Works!')); // This will throw because of (1). - * ``` - * - * For example for (2), - * - * ```ts - * const operation = await input.click(); - * await operation.effect(() => console.log('Works!')); // This will throw because of (2). - * ``` - * - * Tl;dr, effects **must** be added synchronously (no `await` statements between - * the time the operation is created and the effect is added). - * - * @internal - */ -export class Operation extends Promise { - /** - * @internal - */ - static create(fn: () => Awaitable, delay = 0): Operation { - return new Operation((resolve, reject) => { - setTimeout(async () => { - try { - resolve(await fn()); - } catch (error) { - reject(error); - } - }, delay); - }); - } - - #settled = false; - #effects: Array> = []; - #error?: unknown; - - /** - * Adds the given effect. - * - * @example - * - * ```ts - * await input.click().effect(async () => { - * await page.waitForNavigation(); - * }); - * ``` - * - * @param effect - The effect to add. - * @returns `this` for chaining. - * - * @public - */ - effect(effect: () => Awaitable): this { - if (this.#settled) { - throw new Error( - 'Attempted to add effect to a completed operation. Make sure effects are added synchronously after the operation is created.' - ); - } - this.#effects.push( - (async () => { - try { - return await effect(); - } catch (error) { - // Note we can't just push a rejected promise to #effects. This is because - // all rejections must be handled somewhere up in the call stack and since - // this function is synchronous, it is not handled anywhere in the call - // stack. - this.#error = error; - } - })() - ); - return this; - } - - get #effectsPromise(): Promise { - if (this.#error) { - return Promise.reject(this.#error); - } - return Promise.all(this.#effects); - } - - override then( - onfulfilled?: (value: T) => TResult1 | PromiseLike, - onrejected?: (reason: any) => TResult2 | PromiseLike - ): Operation { - return super.then( - value => { - this.#settled = true; - return this.#effectsPromise.then(() => { - if (!onfulfilled) { - return value; - } - return onfulfilled(value); - }, onrejected); - }, - reason => { - this.#settled = true; - if (!onrejected) { - throw reason; - } - return onrejected(reason); - } - ) as Operation; - } -} diff --git a/packages/puppeteer-core/src/common/common.ts b/packages/puppeteer-core/src/common/common.ts index fe3570a9402..00501a784ea 100644 --- a/packages/puppeteer-core/src/common/common.ts +++ b/packages/puppeteer-core/src/common/common.ts @@ -55,7 +55,6 @@ export * from './PredefinedNetworkConditions.js'; export * from './Product.js'; export * from './Puppeteer.js'; export * from './PuppeteerViewport.js'; -export * from './Operation.js'; export * from './SecurityDetails.js'; export * from './Target.js'; export * from './TargetManager.js'; diff --git a/test/src/operation.spec.ts b/test/src/operation.spec.ts deleted file mode 100644 index eb3c237eebb..00000000000 --- a/test/src/operation.spec.ts +++ /dev/null @@ -1,234 +0,0 @@ -/** - * Copyright 2023 Google Inc. All rights reserved. - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -import expect from 'expect'; -import {Operation} from 'puppeteer-core/internal/common/Operation.js'; - -describe('Operation', () => { - it('should work', async () => { - const values: number[] = []; - - await Operation.create(() => { - values.push(1); - }) - .effect(() => { - values.push(2); - }) - .effect(() => { - values.push(3); - }); - - expect(values).toEqual([2, 3, 1]); - }); - - it('should work with error on operation', async () => { - const values: number[] = []; - - let errored: string | undefined; - try { - await Operation.create(() => { - throw new Error('test'); - }) - .effect(() => { - values.push(1); - }) - .effect(() => { - values.push(2); - }); - } catch (error) { - errored = (error as Error).message; - } - - expect(errored).toBe('test'); - expect(values).toEqual([1, 2]); - }); - - it('should work with error on effect', async () => { - const values: number[] = []; - - let errored: string | undefined; - try { - await Operation.create(() => { - values.push(1); - }) - .effect(() => { - throw new Error('test'); - }) - .effect(() => { - values.push(2); - }); - } catch (error) { - errored = (error as Error).message; - } - - expect(errored).toBe('test'); - expect(values).toEqual([2, 1]); - }); - - it('should work with error on both operation and effect', async () => { - const values: number[] = []; - - let errored: string | undefined; - try { - await Operation.create(() => { - throw new Error('test1'); - }) - .effect(() => { - throw new Error('test2'); - }) - .effect(() => { - values.push(1); - }); - } catch (error) { - errored = (error as Error).message; - } - - expect(errored).toBe('test1'); - expect(values).toEqual([1]); - }); - - it('should work with delayed error on operation', async () => { - const values: number[] = []; - - let errored: string | undefined; - try { - await Operation.create(() => { - return new Promise((_, reject) => { - return setTimeout(() => { - reject(new Error('test1')); - }, 10); - }); - }) - .effect(() => { - throw new Error('test2'); - }) - .effect(() => { - values.push(1); - }); - } catch (error) { - errored = (error as Error).message; - } - - expect(errored).toBe('test1'); - expect(values).toEqual([1]); - }); - - it('should work with async error on effects', async () => { - const values: number[] = []; - - let errored: string | undefined; - try { - await Operation.create(() => { - values.push(1); - }) - .effect(async () => { - throw new Error('test'); - }) - .effect(() => { - values.push(2); - }); - } catch (error) { - errored = (error as Error).message; - } - - expect(errored).toBe('test'); - expect(values).toEqual([2, 1]); - }); - - it('should work with then', async () => { - const values: number[] = []; - - const operation = Operation.create(() => { - values.push(1); - }) - .effect(() => { - values.push(2); - }) - .effect(() => { - values.push(3); - }) - .then(() => { - values.push(4); - }); - await operation; - - expect(operation).toBeInstanceOf(Operation); - expect(values).toEqual([2, 3, 1, 4]); - }); - - it('should work with catch', async () => { - const values: number[] = []; - - const operation = Operation.create(() => { - throw new Error('test'); - }) - .effect(() => { - values.push(1); - }) - .effect(() => { - values.push(2); - }) - .catch(() => { - values.push(3); - }); - await operation; - - expect(operation).toBeInstanceOf(Operation); - expect(values).toEqual([1, 2, 3]); - }); - - it('should work with finally', async () => { - const values: number[] = []; - - const operation = Operation.create(() => { - values.push(1); - }) - .effect(() => { - values.push(2); - }) - .effect(() => { - values.push(3); - }) - .finally(() => { - values.push(4); - }); - await operation; - - expect(operation).toBeInstanceOf(Operation); - expect(values).toEqual([2, 3, 1, 4]); - }); - - it('should throw when adding effects on on awaited operation', async () => { - const values: number[] = []; - - const operation = Operation.create(() => { - values.push(1); - }); - await operation; - - expect(() => { - operation.effect(() => { - values.push(2); - }); - }).toThrowError( - new Error( - 'Attempted to add effect to a completed operation. Make sure effects are added synchronously after the operation is created.' - ) - ); - expect(operation).toBeInstanceOf(Operation); - expect(values).toEqual([1]); - }); -});