if (!Symbol.asyncIterator) { Object.defineProperty(Symbol, "asyncIterator", { value: Symbol("Symbol.asyncIterator") }); } export const source: unique symbol = Symbol("GeneratorSource"); export const status: unique symbol = Symbol("GeneratorStatus"); export const result: unique symbol = Symbol("GeneratorResult"); export interface Catchable { catch?( onrejected?: (reason: any) => R | PromiseLike ): Promise; } export interface ThenableGeneratorLike extends PromiseLike, Catchable, Partial> { } export interface ThenableAsyncGeneratorLike extends PromiseLike, Catchable, Partial> { } export type ThenableGeneratorFunction = (...args: TArgs) => ThenableGenerator; export type ThenableAsyncGeneratorFunction = (...args: TArgs) => ThenableAsyncGenerator; export interface ThenableGeneratorFunctionConstructor { (fn: Function): ThenableGeneratorFunction | ThenableAsyncGeneratorFunction; new(fn: Function): ThenableGeneratorFunction | ThenableAsyncGeneratorFunction; /** * @deprecated */ create( fn: (...args: TArgs) => AsyncGenerator | AsyncIterable | Promise ): ThenableAsyncGeneratorFunction; /** * @deprecated */ create( fn: (...args: TArgs) => Generator | Iterable | T ): ThenableGeneratorFunction; } export class Thenable implements PromiseLike, Catchable { protected [source]: any; protected [status]: "suspended" | "closed" | "erred"; protected [result]: any; constructor(_source: any) { this[source] = _source; this[status] = "suspended"; this[result] = void 0; } then( onfulfilled?: ((value: T) => R1 | PromiseLike) | undefined | null, onrejected?: ((reason: any) => R2 | PromiseLike) | undefined | null ): PromiseLike { let res: Promise; if (this[source] === undefined || this[status] === "closed") { res = Promise.resolve(this[result]); } else if (this[status] === "erred") { res = Promise.reject(this[source]); } else if (typeof this[source].then === "function") { res = Promise.resolve(this[source]); } else if (typeof this[source].next === "function") { res = processIterator(this[source]); } else { res = Promise.resolve(this[source]); } this[status] = "closed"; return res .then(value => (this[result] = value)) .then(onfulfilled, onrejected); } catch( onrejected?: (reason: any) => R | PromiseLike ): Promise { return Promise.resolve(this).then(null, onrejected); } } export class ThenableGenerator extends Thenable implements ThenableGeneratorLike { next(...args: [] | [TNext]): IteratorResult { const value = args[0]; let res: IteratorResult; if (this[source] === undefined || this[status] === "closed") { res = { value: void 0, done: true }; } else if (this[status] === "erred") { return this.throw(this[source]); } else if (typeof this[source].next === "function") { res = this[source].next(value); } else { res = { value: this[source], done: true }; } if (res.done === true) { this[status] = "closed"; this[result] = res.value; } return res; } return(value?: TReturn): IteratorResult { this[status] = "closed"; this[result] = value; if (this[source] && typeof this[source].return === "function") { return this[source].return(value); } else { return { value, done: true }; } } throw(err?: any): IteratorResult | never { this[status] = "closed"; if (this[source] && typeof this[source].throw === "function") { return this[source].throw(err) as never; } else { throw err; } } [Symbol.iterator]() { return this; }; } export class ThenableAsyncGenerator extends Thenable implements ThenableAsyncGeneratorLike { next(...args: [] | [TNext]): Promise> { const value = args[0]; let res: Promise>; if (this[source] === undefined || this[status] === "closed") { res = Promise.resolve({ value: void 0, done: true }); } else if (typeof this[source].next === "function") { res = Promise.resolve(this[source].next(value)); } else { res = Promise.resolve(this[source]).then(value => { return { value, done: true }; }); } return res.then(res => { if (res.done === true) { this[status] = "closed"; this[result] = res.value; } return res; }); } return(value?: TReturn | PromiseLike): Promise> { this[status] = "closed"; // The input value may be a promise-like object, using Promise.resolve() // to guarantee the value is resolved. return Promise.resolve(value).then(value => { this[result] = value; if (this[source] && typeof this[source].return === "function") { return Promise.resolve(this[source].return(value)); } else { return Promise.resolve({ value, done: true }); } }); } throw(err?: any): Promise | never> { this[status] = "closed"; if (this[source] && typeof this[source].throw === "function") { return Promise.resolve(this[source].throw(err) as never); } else { return Promise.reject(err); } } [Symbol.asyncIterator]() { return this; } } export const ThenableGeneratorFunction: ThenableGeneratorFunctionConstructor = (function (this: any, fn: Function) { if (!(this instanceof ThenableGeneratorFunction)) { return new (ThenableGeneratorFunction)(fn); } function anonymous(this: any, ...args: any[]) { try { const source = fn.apply(this, args); if (typeof source.then === "function" || isAsyncGenerator(source)) { return new ThenableAsyncGenerator(source); } else { return new ThenableGenerator(source); } } catch (err) { return Object.assign(new ThenableGenerator(err), { [status]: "erred" }); } } // HACK, let the returning function be an instance of // ThenableGeneratorFunction. anonymous.prototype = ThenableGeneratorFunction; anonymous.__proto__ = this; return anonymous; }) as any; Object.setPrototypeOf(ThenableGeneratorFunction, Function); Object.setPrototypeOf(ThenableGeneratorFunction.prototype, Function.prototype); /** * Creates a generator that implements the `PromiseLike` interface so that it can * be awaited in async contexts. */ export function create( fn: (...args: TArgs) => AsyncGenerator | AsyncIterable | Promise ): ThenableAsyncGeneratorFunction; export function create( fn: (...args: TArgs) => Generator | Iterable | T ): ThenableGeneratorFunction; export function create(fn: Function) { return new ThenableGeneratorFunction(fn); } export default create; ThenableGeneratorFunction.create = create; function isAsyncGenerator(obj: any) { return obj !== null && typeof obj === "object" && typeof obj.next === "function" && typeof obj.return === "function" && typeof obj.throw === "function" && typeof obj[Symbol.asyncIterator] === "function"; } function processIterator(iterator: Iterator | AsyncIterator) { return new Promise((resolve, reject) => { function fulfilled(value: any) { try { step(iterator.next(value)); } catch (e) { reject(e); } } function rejected(value: any) { try { step(iterator.throw?.(value)); } catch (e) { reject(e); } } function step(item: any) { Promise.resolve(item).then(result => { result.done ? resolve(result.value) : new Promise(resolve => { resolve(result.value); }).then(fulfilled, rejected); }); } step(iterator.next()); }); }