UNPKG

@ayonli/jsext

Version:

A JavaScript extension package for building strong and modern applications.

272 lines (222 loc) 9.69 kB
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<T> { catch?<R = never>( onrejected?: (reason: any) => R | PromiseLike<R> ): Promise<T | R>; } export interface ThenableGeneratorLike<T = unknown, TReturn = any, TNext = unknown> extends PromiseLike<T>, Catchable<T>, Partial<Generator<T, TReturn, TNext>> { } export interface ThenableAsyncGeneratorLike<T = unknown, TReturn = any, TNext = unknown> extends PromiseLike<T>, Catchable<T>, Partial<AsyncGenerator<T, TReturn, TNext>> { } export type ThenableGeneratorFunction<T = unknown, TReturn = any, TNext = unknown, TArgs extends any[] = any[]> = (...args: TArgs) => ThenableGenerator<T, TReturn, TNext>; export type ThenableAsyncGeneratorFunction<T = unknown, TReturn = any, TNext = unknown, TArgs extends any[] = any[]> = (...args: TArgs) => ThenableAsyncGenerator<T, TReturn, TNext>; export interface ThenableGeneratorFunctionConstructor<T = unknown, TReturn = any, TNext = unknown> { (fn: Function): ThenableGeneratorFunction<T, TReturn, TNext> | ThenableAsyncGeneratorFunction<T, TReturn, TNext>; new(fn: Function): ThenableGeneratorFunction<T, TReturn, TNext> | ThenableAsyncGeneratorFunction<T, TReturn, TNext>; /** * @deprecated */ create<T = unknown, TReturn = any, TNext = unknown, TArgs extends any[] = any[]>( fn: (...args: TArgs) => AsyncGenerator<T, TReturn, TNext> | AsyncIterable<T> | Promise<T> ): ThenableAsyncGeneratorFunction<T, TReturn, TNext, TArgs>; /** * @deprecated */ create<T = unknown, TReturn = any, TNext = unknown, TArgs extends any[] = any[]>( fn: (...args: TArgs) => Generator<T, TReturn, TNext> | Iterable<T> | T ): ThenableGeneratorFunction<T, TReturn, TNext, TArgs>; } export class Thenable<T = any> implements PromiseLike<T>, Catchable<T> { 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<R1 = T, R2 = never>( onfulfilled?: ((value: T) => R1 | PromiseLike<R1>) | undefined | null, onrejected?: ((reason: any) => R2 | PromiseLike<R2>) | undefined | null ): PromiseLike<R1 | R2> { let res: Promise<any>; 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<R = never>( onrejected?: (reason: any) => R | PromiseLike<R> ): Promise<T | R> { return Promise.resolve(this).then(null, onrejected); } } export class ThenableGenerator<T = unknown, TReturn = any, TNext = unknown> extends Thenable<T> implements ThenableGeneratorLike<T, TReturn, TNext> { next(...args: [] | [TNext]): IteratorResult<T> { const value = args[0]; let res: IteratorResult<T>; 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<T> { 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<T, TReturn> | 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<T = unknown, TReturn = any, TNext = unknown> extends Thenable<T> implements ThenableAsyncGeneratorLike<T, TReturn, TNext> { next(...args: [] | [TNext]): Promise<IteratorResult<T, TReturn>> { const value = args[0]; let res: Promise<IteratorResult<any>>; 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<TReturn>): Promise<IteratorResult<T, TReturn>> { 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<IteratorResult<T, TReturn> | 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 (<any>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<T = unknown, TReturn = any, TNext = unknown, TArgs extends any[] = any[]>( fn: (...args: TArgs) => AsyncGenerator<T, TReturn, TNext> | AsyncIterable<T> | Promise<T> ): ThenableAsyncGeneratorFunction<T, TReturn, TNext, TArgs>; export function create<T = unknown, TReturn = any, TNext = unknown, TArgs extends any[] = any[]>( fn: (...args: TArgs) => Generator<T, TReturn, TNext> | Iterable<T> | T ): ThenableGeneratorFunction<T, TReturn, TNext, TArgs>; 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<any> | AsyncIterator<any>) { return new Promise<any>((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()); }); }