import { isPromise } from '../jsutils/isPromise.ts';
import type { PromiseOrValue } from '../jsutils/PromiseOrValue.ts';
const asyncDispose: typeof Symbol.asyncDispose =
Symbol.asyncDispose ?? Symbol.for('Symbol.asyncDispose');
export function withConcurrentAbruptClose<T>(
generator: AsyncGenerator<T, void, void>,
beforeReturn: () => PromiseOrValue<void>,
beforeThrow: (error?: unknown) => PromiseOrValue<void> = beforeReturn,
): AsyncGenerator<T, void, void> {
let completed = false;
let abruptCloseRequested = false;
const runAbruptCloseFn = (fn: () => PromiseOrValue<void>) => {
if (completed || abruptCloseRequested) {
return;
}
abruptCloseRequested = true;
return ignoreErrors(fn);
};
return {
[Symbol.asyncIterator]() {
return this;
},
next() {
const result = generator.next();
result
.then((iteration) => {
if (iteration.done) {
completed = true;
}
})
.catch(() => undefined);
return result;
},
async return() {
await runAbruptCloseFn(beforeReturn);
return generator.return();
},
async throw(error?: unknown) {
await runAbruptCloseFn(() => beforeThrow(error));
return generator.throw(error);
},
async [asyncDispose]() {
await runAbruptCloseFn(beforeReturn);
if (typeof generator[asyncDispose] === 'function') {
await generator[asyncDispose]();
}
},
};
}
function ignoreErrors(
fn: () => PromiseOrValue<unknown>,
): PromiseOrValue<unknown> {
try {
const result = fn();
if (isPromise(result)) {
return result.catch(() => {
});
}
} catch {
}
}