Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
29 changes: 29 additions & 0 deletions packages/shared/src/iterator.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,35 @@ it('isAsyncIteratorObject', () => {
expect(isAsyncIteratorObject(gen2())).toBe(false)
})

it('uses Symbol.for("Symbol.asyncDispose") when Symbol.asyncDispose is not native', async ({
onTestFinished,
}) => {
onTestFinished(() => {
vi.unstubAllGlobals()
vi.resetModules()
})

// Symbol.asyncDispose is non-configurable, so a Proxy over Symbol itself would break the get invariant
const RealSymbol = Symbol
vi.stubGlobal(
'Symbol',
new Proxy(function () {} as any, {
apply: (_target, _this, args) => RealSymbol(...args),
get: (_target, key) => (key === 'asyncDispose' ? undefined : Reflect.get(RealSymbol, key)),
}),
)
vi.resetModules()
const { AsyncIteratorClass } = await import('./iterator')
vi.unstubAllGlobals()

const cleanup = vi.fn()
const iterator = new AsyncIteratorClass(async () => ({ done: false, value: 1 }), cleanup)

await (iterator as any)[RealSymbol.for('Symbol.asyncDispose')]()
expect(cleanup).toHaveBeenCalledTimes(1)
expect(cleanup).toHaveBeenCalledWith({ kind: 'cancelled' })
})

describe('asyncIteratorClass', () => {
const next = vi.fn()
const cleanup = vi.fn()
Expand Down
7 changes: 3 additions & 4 deletions packages/shared/src/iterator.ts
Original file line number Diff line number Diff line change
Expand Up @@ -18,12 +18,11 @@ export interface AsyncIteratorClassNextFn<T, TReturn> {
(): Promise<IteratorResult<T, TReturn>>
}

const fallbackAsyncDisposeSymbol: unique symbol = Symbol.for('asyncDispose')
/* v8 ignore start */
// same key as esbuild's `__using`, Babel's `usingCtx` and core-js use without a native symbol
const fallbackAsyncDisposeSymbol: unique symbol = Symbol.for('Symbol.asyncDispose')
const asyncDisposeSymbol: typeof Symbol extends { asyncDispose: infer T }
? T
: typeof fallbackAsyncDisposeSymbol = (Symbol as any).asyncDispose ?? fallbackAsyncDisposeSymbol
/* v8 ignore end */

export class AsyncIteratorClass<T, TReturn = unknown, TNext = unknown>
implements AsyncIteratorObject<T, TReturn, TNext>, AsyncGenerator<T, TReturn, TNext>
Expand Down Expand Up @@ -99,7 +98,7 @@ export class AsyncIteratorClass<T, TReturn = unknown, TNext = unknown>
}

/**
* asyncDispose symbol only available in esnext, we should fallback to Symbol.for('asyncDispose')
* Uses the native Symbol.asyncDispose, or Symbol.for('Symbol.asyncDispose') on runtimes without it
*/
async [asyncDisposeSymbol](): Promise<void> {
this.isDone = true
Expand Down
Loading