UNPKG

@tsdotnet/promises

Version:

An extended A+ promise library with lazy and synchronous promises.

1,000 lines 36.5 kB
"use strict"; /*! * @author electricessence / https://github.com/electricessence/ * Licensing: MIT * Although most of the following code is written from scratch, it is * heavily influenced by Q (https://github.com/kriskowal/q) and uses some of Q's spec. */ /* * Resources: * https://promisesaplus.com/ * https://github.com/kriskowal/q */ Object.defineProperty(exports, "__esModule", { value: true }); exports.Promise = exports.PromiseCollection = exports.ArrayPromise = exports.TSDNPromise = exports.Rejected = exports.Fulfilled = exports.Resolved = exports.Resolvable = exports.PromiseBase = exports.PromiseState = void 0; const tslib_1 = require("tslib"); const disposable_1 = require("@tsdotnet/disposable"); const ObjectDisposedException_1 = tslib_1.__importDefault(require("@tsdotnet/disposable/dist/ObjectDisposedException")); const ArgumentException_1 = tslib_1.__importDefault(require("@tsdotnet/exceptions/dist/ArgumentException")); const ArgumentNullException_1 = tslib_1.__importDefault(require("@tsdotnet/exceptions/dist/ArgumentNullException")); const InvalidOperationException_1 = tslib_1.__importDefault(require("@tsdotnet/exceptions/dist/InvalidOperationException")); const object_pool_1 = tslib_1.__importDefault(require("@tsdotnet/object-pool")); const defer_1 = tslib_1.__importDefault(require("@tsdotnet/threading/dist/defer")); const deferImmediate_1 = tslib_1.__importDefault(require("@tsdotnet/threading/dist/deferImmediate")); const type_1 = tslib_1.__importDefault(require("@tsdotnet/type")); const VOID0 = void 0, NULL = null, PROMISE = 'Promise', PROMISE_STATE = PROMISE + 'State', THEN = 'then', TARGET = 'target'; function isPromise(value) { return type_1.default.hasMemberOfType(value, THEN, "function" /* type.Value.Function */); } function resolve(value, resolver, promiseFactory) { const nextValue = resolver ? resolver(value) : value; return nextValue && isPromise(nextValue) ? TSDNPromise.wrap(nextValue) : promiseFactory(nextValue); } function handleResolution(p, value, resolver) { try { const v = resolver ? resolver(value) : value; if (p) { //noinspection JSIgnoredPromiseFromCall p.resolve(v); } return null; } catch (ex) { if (p) { //noinspection JSIgnoredPromiseFromCall p.reject(ex); } return ex; } } function handleResolutionMethods(targetFulfill, targetReject, value, resolver) { try { const v = resolver ? resolver(value) : value; if (targetFulfill) targetFulfill(v); } catch (ex) { if (targetReject) targetReject(ex); } } function handleDispatch(p, onFulfilled, onRejected) { // noinspection SuspiciousInstanceOfGuard if (p instanceof PromiseBase) { p.doneNow(onFulfilled, onRejected); } else { p.then(onFulfilled, onRejected); } } function handleSyncIfPossible(p, onFulfilled, onRejected) { // noinspection SuspiciousInstanceOfGuard if (p instanceof PromiseBase) return p.thenSynchronous(onFulfilled, onRejected); else return p.then(onFulfilled, onRejected); } function newODE() { return new ObjectDisposedException_1.default('TSDNPromise', 'An underlying promise-result was disposed.'); } class PromiseState extends disposable_1.DisposableBase { constructor(_state, _result, _error) { super(PROMISE_STATE); this._state = _state; this._result = _result; this._error = _error; } get state() { return this._state; } get isPending() { return this.getState() === TSDNPromise.State.Pending; } get isSettled() { return this.getState() != TSDNPromise.State.Pending; // Will also include undefined==0 aka disposed!=resolved. } get isFulfilled() { return this.getState() === TSDNPromise.State.Fulfilled; } get isRejected() { return this.getState() === TSDNPromise.State.Rejected; } get result() { this.throwIfDisposed(); return this.getResult(); } get error() { this.throwIfDisposed(); return this.getError(); } _onDispose() { this._state = VOID0; this._result = VOID0; this._error = VOID0; } getState() { return this._state; } /* * Providing overrides allows for special defer or lazy sub classes. */ getResult() { return this._result; } getError() { return this._error; } } exports.PromiseState = PromiseState; class PromiseBase extends PromiseState { //readonly [Symbol.toStringTag]: "Promise"; constructor() { super(TSDNPromise.State.Pending); this._disposableObjectName = PROMISE; } /** * Same as 'thenSynchronous' but does not return the result. Returns the current promise instead. * You may not need an additional promise result, and this will not create a new one. * @param onFulfilled * @param onRejected */ thenThis(onFulfilled, onRejected) { this.doneNow(onFulfilled, onRejected); return this; } /** * Standard .then method that defers execution until resolved. * @param onFulfilled * @param onRejected * @returns {TSDNPromise} */ then(onFulfilled, onRejected) { this.throwIfDisposed(); return new TSDNPromise((resolve, reject) => { this.doneNow(result => handleResolutionMethods(resolve, reject, result, onFulfilled), error => onRejected ? handleResolutionMethods(resolve, reject, error, onRejected) : reject(error)); }); } /** * Same as .then but doesn't trap errors. Exceptions may end up being fatal. * @param onFulfilled * @param onRejected * @returns {TSDNPromise} */ thenAllowFatal(onFulfilled, onRejected) { this.throwIfDisposed(); return new TSDNPromise((resolve, reject) => { this.doneNow(result => resolve((onFulfilled ? onFulfilled(result) : result)), error => reject(onRejected ? onRejected(error) : error)); }); } /** * .done is provided as a non-standard means that maps to similar functionality in other promise libraries. * As stated by promisejs.org: 'then' is to 'done' as 'map' is to 'forEach'. * @param onFulfilled * @param onRejected */ done(onFulfilled, onRejected) { (0, defer_1.default)(() => this.doneNow(onFulfilled, onRejected)); } /** * Will yield for a number of milliseconds from the time called before continuing. * @param milliseconds * @returns A promise that yields to the current execution and executes after a delay. */ delayFromNow(milliseconds = 0) { this.throwIfDisposed(); return new TSDNPromise((resolve, reject) => { (0, defer_1.default)(() => { this.doneNow(v => resolve(v), e => reject(e)); }, milliseconds); }, true // Since the resolve/reject is deferred. ); } /** * Will yield for a number of milliseconds from after this promise resolves. * If the promise is already resolved, the delay will start from now. * @param milliseconds * @returns A promise that yields to the current execution and executes after a delay. */ delayAfterResolve(milliseconds = 0) { this.throwIfDisposed(); if (this.isSettled) return this.delayFromNow(milliseconds); return new TSDNPromise((resolve, reject) => { this.doneNow(v => (0, defer_1.default)(() => resolve(v), milliseconds), e => (0, defer_1.default)(() => reject(e), milliseconds)); }, true // Since the resolve/reject is deferred. ); } /** * Shortcut for trapping a rejection. * @param onRejected * @returns {PromiseBase<TResult>} */ 'catch'(onRejected) { return this.then(VOID0, onRejected); } /** * Shortcut for trapping a rejection but will allow exceptions to propagate within the onRejected handler. * @param onRejected * @returns {PromiseBase<TResult>} */ catchAllowFatal(onRejected) { return this.thenAllowFatal(VOID0, onRejected); } /** * Shortcut to for handling either resolve or reject. * @param fin * @returns {PromiseBase<TResult>} */ 'finally'(fin) { return this.then(fin, fin); } /** * Shortcut to for handling either resolve or reject but will allow exceptions to propagate within the handler. * @param fin * @returns {PromiseBase<TResult>} */ finallyAllowFatal(fin) { return this.thenAllowFatal(fin, fin); } /** * Shortcut to for handling either resolve or reject. Returns the current promise instead. * You may not need an additional promise result, and this will not create a new one. * @param fin * @param synchronous * @returns {PromiseBase} */ finallyThis(fin, synchronous) { const f = synchronous ? fin : () => (0, deferImmediate_1.default)(fin); this.doneNow(f, f); return this; } } exports.PromiseBase = PromiseBase; class Resolvable extends PromiseBase { doneNow(onFulfilled, onRejected) { this.throwIfDisposed(); switch (this.state) { case TSDNPromise.State.Fulfilled: if (onFulfilled) onFulfilled(this._result); break; case TSDNPromise.State.Rejected: if (onRejected) onRejected(this._error); break; } } thenSynchronous(onFulfilled, onRejected) { this.throwIfDisposed(); try { switch (this.state) { case TSDNPromise.State.Fulfilled: return onFulfilled ? resolve(this._result, onFulfilled, TSDNPromise.resolve) : this; // Provided for catch cases. case TSDNPromise.State.Rejected: return onRejected ? resolve(this._error, onRejected, TSDNPromise.resolve) : this; } } catch (ex) { return new Rejected(ex); } throw new Error('Invalid state for a resolved promise.'); } } exports.Resolvable = Resolvable; /** * The simplest usable version of a promise which returns synchronously the resolved state provided. */ class Resolved extends Resolvable { constructor(state, result, error) { super(); this._result = result; this._error = error; this._state = state; } } exports.Resolved = Resolved; /** * A fulfilled Resolved<T>. Provided for readability. */ class Fulfilled extends Resolved { constructor(value) { super(TSDNPromise.State.Fulfilled, value); } } exports.Fulfilled = Fulfilled; /** * A rejected Resolved<T>. Provided for readability. */ class Rejected extends Resolved { constructor(error) { super(TSDNPromise.State.Rejected, VOID0, error); } } exports.Rejected = Rejected; /** * Provided as a means for extending the interface of other PromiseLike<T> objects. */ class PromiseWrapper extends Resolvable { constructor(_target) { super(); this._target = _target; if (!_target) throw new ArgumentNullException_1.default(TARGET); if (!isPromise(_target)) throw new ArgumentException_1.default(TARGET, 'Must be a promise-like object.'); _target.then((v) => { this._state = TSDNPromise.State.Fulfilled; this._result = v; this._error = VOID0; this._target = VOID0; }, e => { this._state = TSDNPromise.State.Rejected; this._error = e; this._target = VOID0; }); } thenSynchronous(onFulfilled, onRejected) { this.throwIfDisposed(); const t = this._target; if (!t) return super.thenSynchronous(onFulfilled, onRejected); return new TSDNPromise((resolve, reject) => { handleDispatch(t, result => handleResolutionMethods(resolve, reject, result, onFulfilled), error => onRejected ? handleResolutionMethods(resolve, null, error, onRejected) : reject(error)); }, true); } doneNow(onFulfilled, onRejected) { this.throwIfDisposed(); const t = this._target; if (t) handleDispatch(t, onFulfilled, onRejected); else super.doneNow(onFulfilled, onRejected); } _onDispose() { super._onDispose(); this._target = VOID0; } } /** * This promise class that facilitates pending resolution. */ class TSDNPromise extends Resolvable { constructor(resolver, forceSynchronous = false) { super(); if (resolver) this.resolveUsing(resolver, forceSynchronous); } thenSynchronous(onFulfilled, onRejected) { this.throwIfDisposed(); // Already fulfilled? if (this._state) return super.thenSynchronous(onFulfilled, onRejected); const p = new TSDNPromise(); (this._waiting || (this._waiting = [])) .push(pools.PromiseCallbacks.init(onFulfilled, onRejected, p)); return p; } doneNow(onFulfilled, onRejected) { this.throwIfDisposed(); // Already fulfilled? if (this._state) return super.doneNow(onFulfilled, onRejected); (this._waiting || (this._waiting = [])) .push(pools.PromiseCallbacks.init(onFulfilled, onRejected)); } resolveUsing(resolver, forceSynchronous = false) { if (!resolver) throw new ArgumentNullException_1.default('resolver'); if (this._resolvedCalled) throw new InvalidOperationException_1.default('.resolve() already called.'); if (this.state) throw new InvalidOperationException_1.default('Already resolved: ' + TSDNPromise.State[this.state]); this._resolvedCalled = true; let state = 0; const rejectHandler = (reason) => { if (state) { // Someone else's promise handling down stream could double call this. :\ console.warn(state == -1 ? 'Rejection called multiple times' : 'Rejection called after fulfilled.'); } else { state = -1; this._resolvedCalled = false; this.reject(reason); } }; const fulfillHandler = (v) => { if (state) { // Someone else's promise handling down stream could double call this. :\ console.warn(state == 1 ? 'Fulfill called multiple times' : 'Fulfill called after rejection.'); } else { state = 1; this._resolvedCalled = false; this.resolve(v); } }; // There are some performance edge cases where there caller is not blocking upstream and does not need to defer. if (forceSynchronous) resolver(fulfillHandler, rejectHandler); else (0, deferImmediate_1.default)(() => resolver(fulfillHandler, rejectHandler)); } resolve(result, throwIfSettled = false) { this.throwIfDisposed(); if (result == this) throw new InvalidOperationException_1.default('Cannot resolve a promise as itself.'); if (this._state) { // Same value? Ignore... if (!throwIfSettled || this._state == TSDNPromise.State.Fulfilled && this._result === result) return; throw new InvalidOperationException_1.default('Changing the fulfilled state/value of a promise is not supported.'); } if (this._resolvedCalled) { if (throwIfSettled) throw new InvalidOperationException_1.default('.resolve() already called.'); return; } this._resolveInternal(result); } reject(error, throwIfSettled = false) { this.throwIfDisposed(); if (this._state) { // Same value? Ignore... if (!throwIfSettled || this._state == TSDNPromise.State.Rejected && this._error === error) return; throw new InvalidOperationException_1.default('Changing the rejected state/value of a promise is not supported.'); } if (this._resolvedCalled) { if (throwIfSettled) throw new InvalidOperationException_1.default('.resolve() already called.'); return; } this._rejectInternal(error); } _onDispose() { super._onDispose(); this._resolvedCalled = VOID0; } _emitDisposalRejection(p) { const d = p.wasDisposed; if (d) this._rejectInternal(newODE()); return d; } _resolveInternal(result) { if (this.wasDisposed) return; // Note: Avoid recursion if possible. // Check ahead of time for resolution and resolve appropriately // noinspection SuspiciousInstanceOfGuard while (result instanceof PromiseBase) { const r = result; if (this._emitDisposalRejection(r)) return; switch (r.state) { case TSDNPromise.State.Pending: r.doneNow(v => this._resolveInternal(v), e => this._rejectInternal(e)); return; case TSDNPromise.State.Rejected: this._rejectInternal(r.error); return; case TSDNPromise.State.Fulfilled: result = r.result; break; } } if (isPromise(result)) { result.then(v => this._resolveInternal(v), e => this._rejectInternal(e)); } else { this._state = TSDNPromise.State.Fulfilled; this._result = result; this._error = VOID0; const o = this._waiting; if (o) { this._waiting = VOID0; for (const c of o) { const { onFulfilled, promise } = c; pools.PromiseCallbacks.recycle(c); //let ex = handleResolution(promise, result, onFulfilled); //if(!p && ex) console.error("Unhandled exception in onFulfilled:",ex); } o.length = 0; } } } _rejectInternal(error) { if (this.wasDisposed) return; this._state = TSDNPromise.State.Rejected; this._error = error; const o = this._waiting; if (o) { this._waiting = null; // null = finished. undefined = hasn't started. for (const c of o) { const { onRejected, promise } = c; pools.PromiseCallbacks.recycle(c); if (onRejected) { //let ex = handleResolution(promise, error, onRejected); //if(!p && ex) console.error("Unhandled exception in onRejected:",ex); } else if (promise) { //noinspection JSIgnoredPromiseFromCall promise.reject(error); } } o.length = 0; } } } exports.TSDNPromise = TSDNPromise; exports.Promise = TSDNPromise; /** * By providing an ArrayPromise we expose useful methods/shortcuts for dealing with array results. */ class ArrayPromise extends TSDNPromise { static fulfilled(value) { // eslint-disable-next-line @typescript-eslint/no-unused-vars return new ArrayPromise(() => value, true); } /** * Simplifies the use of a map function on an array of results when the source is assured to be an array. * @param transform * @returns {PromiseBase<Array<any>>} */ map(transform) { this.throwIfDisposed(); return new ArrayPromise(resolve => { this.doneNow(result => resolve(result.map(transform))); }, true); } /** * Simplifies the use of a reduce function on an array of results when the source is assured to be an array. * @param reduction * @param initialValue * @returns {PromiseBase<any>} */ reduce(reduction, initialValue) { return this .thenSynchronous(result => result.reduce(reduction, initialValue)); } } exports.ArrayPromise = ArrayPromise; const PROMISE_COLLECTION = 'PromiseCollection'; /** * A Promise collection exposes useful methods for handling a collection of promises and their results. */ class PromiseCollection extends disposable_1.DisposableBase { constructor(source) { super(PROMISE_COLLECTION); this._source = source && source.slice() || []; } /** * Returns a copy of the source promises. * @returns {PromiseLike<PromiseLike<any>>[]} */ get promises() { this.throwIfDisposed(); return this._source.slice(); } /** * Returns a promise that is fulfilled with an array containing the fulfillment value of each promise, or is rejected with the same rejection reason as the first promise to be rejected. * @returns {PromiseBase<any>} */ all() { this.throwIfDisposed(); return TSDNPromise.all(this._source); } /** * Creates a Promise that is resolved or rejected when any of the provided Promises are resolved * or rejected. * @returns {PromiseBase<any>} A new Promise. */ race() { this.throwIfDisposed(); return TSDNPromise.race(this._source); } /** * Returns a promise that is fulfilled with array of provided promises when all provided promises have resolved (fulfill or reject). * Unlike .all this method waits for all rejections as well as fulfillment. * @returns {PromiseBase<PromiseLike<any>[]>} */ waitAll() { this.throwIfDisposed(); return TSDNPromise.waitAll(this._source); } /** * Waits for all the values to resolve and then applies a transform. * @param transform * @returns {PromiseBase<Array<any>>} */ map(transform) { this.throwIfDisposed(); return new ArrayPromise(resolve => { this.all() .doneNow(result => resolve(result.map(transform))); }, true); } /** * Applies a transform to each promise and defers the result. * Unlike map, this doesn't wait for all promises to resolve, ultimately improving the async nature of the request. * @param transform * @returns {PromiseCollection<U>} */ pipe(transform) { this.throwIfDisposed(); return new PromiseCollection(this._source.map(p => handleSyncIfPossible(p, transform))); } /** * Behaves like array reduce. * Creates the promise chain necessary to produce the desired result. * @param reduction * @param initialValue * @returns {PromiseBase<PromiseLike<any>>} */ reduce(reduction, initialValue) { this.throwIfDisposed(); // eslint-disable-next-line @typescript-eslint/ban-ts-comment // @ts-ignore return TSDNPromise.wrap(this._source.reduce((previous, current, i, array) => handleSyncIfPossible(previous, (p) => handleSyncIfPossible(current, (c) => reduction(p, c, i, array))), isPromise(initialValue) ? initialValue : new Fulfilled(initialValue))); } _onDispose() { super._onDispose(); this._source.length = 0; this._source = null; } } exports.PromiseCollection = PromiseCollection; var pools; (function (pools) { // export module pending // { // // // var pool:ObjectPool<Promise<any>>; // // function getPool() // { // return pool || (pool = new ObjectPool<Promise<any>>(40, factory, c=>c.dispose())); // } // // function factory():Promise<any> // { // return new Promise(); // } // // export function get():Promise<any> // { // var p:any = getPool().take(); // p.__wasDisposed = false; // p._state = Promise.State.Pending; // return p; // } // // export function recycle<T>(c:Promise<T>):void // { // if(c) getPool().add(c); // } // // } // // export function recycle<T>(c:PromiseBase<T>):void // { // if(!c) return; // if(c instanceof Promise && c.constructor==Promise) pending.recycle(c); // else c.dispose(); // } let PromiseCallbacks; (function (PromiseCallbacks) { let pool; //noinspection JSUnusedLocalSymbols function getPool() { return pool || (pool = new object_pool_1.default(factory, c => { c.onFulfilled = NULL; c.onRejected = NULL; c.promise = NULL; }, 40)); } function factory() { return { onFulfilled: NULL, onRejected: NULL, promise: NULL }; } function init(onFulfilled, onRejected, promise) { const c = getPool().take(); c.onFulfilled = onFulfilled || undefined; c.onRejected = onRejected || undefined; c.promise = promise; return c; } PromiseCallbacks.init = init; function recycle(c) { getPool().give(c); } PromiseCallbacks.recycle = recycle; })(PromiseCallbacks = pools.PromiseCallbacks || (pools.PromiseCallbacks = {})); })(pools || (pools = {})); (function (TSDNPromise) { /** * The state of a promise. * https://github.com/domenic/promises-unwrapping/blob/master/docs/states-and-fates.md * If a promise is disposed the value will be undefined which will also evaluate (promise.state)==false. */ let State; (function (State) { State[State["Pending"] = 0] = "Pending"; State[State["Fulfilled"] = 1] = "Fulfilled"; State[State["Rejected"] = -1] = "Rejected"; })(State = TSDNPromise.State || (TSDNPromise.State = {})); Object.freeze(State); function factory(e) { return new TSDNPromise(e); } TSDNPromise.factory = factory; /** * Takes a set of promises and returns a PromiseCollection. * @param {PromiseLike<any> | PromiseLike<any>[]} first * @param {PromiseLike<any>} rest * @return {PromiseCollection<any>} */ function group(first, ...rest) { if (!first && !rest.length) throw new ArgumentNullException_1.default('promises'); return new PromiseCollection(((first) instanceof (Array) ? first : [first]) .concat(rest)); } TSDNPromise.group = group; /** * Returns a promise that is fulfilled with an array containing the fulfillment value of each promise, or is rejected with the same rejection reason as the first promise to be rejected. * @param {PromiseLike<any> | PromiseLike<any>[]} first * @param {PromiseLike<any>} rest * @return {ArrayPromise<any>} */ function all(first, ...rest) { if (!first && !rest.length) throw new ArgumentNullException_1.default('promises'); let promises = ((first) instanceof (Array) ? first : [first]).concat(rest); // yay a copy! if (!promises.length || promises.every(v => !v)) return new ArrayPromise(r => r(promises), true); // it's a new empty, reuse it. :| // Eliminate deferred and take the parent since all .then calls happen on next cycle anyway. return new ArrayPromise((resolve, reject) => { const result = []; const len = promises.length; result.length = len; // Using a set instead of -- a number is more reliable if just in case one of the provided promises resolves twice. let remaining = new Set(promises.map((v, i) => i)); // get all the indexes... const cleanup = () => { reject = VOID0; resolve = VOID0; promises.length = 0; promises = VOID0; remaining.clear(); remaining = VOID0; }; const checkIfShouldResolve = () => { const r = resolve; if (r && !remaining.size) { cleanup(); r(result); } }; const onFulfill = (v, i) => { if (resolve != null) { result[i] = v; remaining.delete(i); checkIfShouldResolve(); } }; const onReject = (e) => { const r = reject; if (r) { cleanup(); r(e); } }; for (let i = 0; remaining && i < len; i++) { const p = promises[i]; if (p) p.then(v => onFulfill(v, i), onReject); else remaining.delete(i); checkIfShouldResolve(); } }); } TSDNPromise.all = all; /** * Returns a promise that is fulfilled with array of provided promises when all provided promises have resolved (fulfill or reject). * Unlike .all this method waits for all rejections as well as fulfillment. * @param {PromiseLike<any> | PromiseLike<any>[]} first * @param {PromiseLike<any>} rest * @return {ArrayPromise<PromiseLike<any>>} */ function waitAll(first, ...rest) { if (!first && !rest.length) throw new ArgumentNullException_1.default('promises'); const promises = ((first) instanceof (Array) ? first : [first]).concat(rest); // yay a copy! if (!promises.length || promises.every(v => !v)) return new ArrayPromise(r => r(promises), true); // it's a new empty, reuse it. :| // Eliminate deferred and take the parent since all .then calls happen on next cycle anyway. return new ArrayPromise(resolve => { const len = promises.length; // Using a set instead of -- a number is more reliable if just in case one of the provided promises resolves twice. let remaining = new Set(promises.map((v, i) => i)); // get all the indexes... const cleanup = () => { resolve = NULL; remaining.clear(); remaining = NULL; }; const checkIfShouldResolve = () => { const r = resolve; if (r && !remaining.size) { cleanup(); r(promises); } }; const onResolved = (i) => { if (remaining) { remaining.delete(i); checkIfShouldResolve(); } }; for (let i = 0; remaining && i < len; i++) { const p = promises[i]; if (p) { p.then(() => onResolved(i), () => onResolved(i)); } else onResolved(i); } }); } TSDNPromise.waitAll = waitAll; /** * Creates a Promise that is resolved or rejected when any of the provided Promises are resolved * or rejected. * @param {PromiseLike<any> | PromiseLike<any>[]} first * @param {PromiseLike<any>} rest * @return {PromiseBase<any>} */ function race(first, ...rest) { let promises = first && ((first) instanceof (Array) ? first : [first]).concat(rest); // yay a copy? if (!promises || !promises.length || !(promises = promises.filter(v => v != null)).length) throw new ArgumentException_1.default('Nothing to wait for.'); const len = promises.length; // Only one? Nothing to race. if (len == 1) return wrap(promises[0]); // Look for already resolved promises and the first one wins. for (let i = 0; i < len; i++) { const p = promises[i]; if (p instanceof PromiseBase && p.isSettled) return p; } return new TSDNPromise((resolve, reject) => { const cleanup = () => { reject = NULL; resolve = NULL; promises.length = 0; promises = NULL; }; const onResolve = (r, v) => { if (r) { cleanup(); r(v); } }; const onFulfill = (v) => onResolve(resolve, v); const onReject = (e) => onResolve(reject, e); for (const p of promises) { if (!resolve) break; p.then(onFulfill, onReject); } }); } TSDNPromise.race = race; /** * Creates a new resolved promise for the provided value. * @param value A value or promise. * @returns A promise whose internal state matches the provided promise. */ function resolve(value) { return isPromise(value) ? wrap(value) : new Fulfilled(value); } TSDNPromise.resolve = resolve; /** * Syntactic shortcut for avoiding 'new'. * @param resolver * @param forceSynchronous * @returns {TSDNPromise} */ function using(resolver, forceSynchronous = false) { return new TSDNPromise(resolver, forceSynchronous); } TSDNPromise.using = using; function resolveAll(first, ...rest) { if (!first && !rest.length) throw new ArgumentNullException_1.default('resolutions'); return new PromiseCollection(((first) instanceof (Array) ? first : [first]) .concat(rest) .map((v) => resolve(v))); } TSDNPromise.resolveAll = resolveAll; /** * Creates a PromiseCollection containing promises that will resolve on the next tick using the transform function. * This utility function does not chain promises together to create the result, * it only uses one promise per transform. * @param source * @param transform * @returns {PromiseCollection<T>} */ function map(source, transform) { return new PromiseCollection(source.map(d => new TSDNPromise((r, j) => { try { r(transform(d)); } catch (ex) { j(ex); } }))); } TSDNPromise.map = map; /** * Creates a new rejected promise for the provided reason. * @param reason The reason the promise was rejected. * @returns A new rejected Promise. */ function reject(reason) { return new Rejected(reason); } TSDNPromise.reject = reject; /** * Takes any Promise-Like object and ensures an extended version of it from this module. * @param target The Promise-Like object * @returns A new target that simply extends the target. */ function wrap(target) { if (!target) throw new ArgumentNullException_1.default(TARGET); // noinspection SuspiciousInstanceOfGuard return isPromise(target) ? (target instanceof PromiseBase ? target : new PromiseWrapper(target)) : new Fulfilled(target); } TSDNPromise.wrap = wrap; /** * A function that acts like a 'then' method (aka then-able) can be extended by providing a function that takes an onFulfill and onReject. * @param then * @returns {PromiseWrapper<T>} */ function createFrom(then) { if (!then) throw new ArgumentNullException_1.default(THEN); return new PromiseWrapper({ then: then }); } TSDNPromise.createFrom = createFrom; })(TSDNPromise = exports.TSDNPromise || (exports.TSDNPromise = {})); exports.Promise = TSDNPromise; exports.default = TSDNPromise; //# sourceMappingURL=Promise.js.map