module BABYLON { enum PromiseStates { Pending, Fulfilled, Rejected } class FulFillmentAgregator { public count = 0; public target = 0; public rootPromise: InternalPromise; public results:any[] = []; } class InternalPromise { private _state = PromiseStates.Pending; private _result?: Nullable; private _reason: any; private _child: InternalPromise; private _onFulfilled?: (fulfillment?: Nullable) => Nullable> | T; private _onRejected?: (reason: any) => void; private _rejectWasConsumed = false; public get state(): PromiseStates { return this._state; } public isFulfilled(): boolean { return this._state === PromiseStates.Fulfilled; } public isRejected(): boolean { return this._state === PromiseStates.Rejected; } public isPending(): boolean { return this._state === PromiseStates.Pending; } public value(): Nullable | undefined { if (!this.isFulfilled()) { throw new Error("Promise is not fulfilled"); } return this._result; } public reason(): any { if (!this.isRejected()) { throw new Error("Promise is not rejected"); } return this._reason; } public constructor(resolver?: ( resolve:(value?: Nullable) => void, reject: (reason: any) => void ) => void) { if (!resolver) { return; } try { resolver((value?: Nullable) => { this._resolve(value); }, (reason: any) => { this._reject(reason); }); } catch(e) { this._reject((e).message); } } public catch(onRejected: (reason: any) => void): InternalPromise { return this.then(undefined, onRejected); } public then(onFulfilled?: (fulfillment?: Nullable) => Nullable> | T, onRejected?: (reason: any) => void): InternalPromise { let newPromise = new InternalPromise(); newPromise._onFulfilled = onFulfilled; newPromise._onRejected = onRejected; // Composition this._child = newPromise; if (this._state !== PromiseStates.Pending) { if (this._state === PromiseStates.Fulfilled || this._rejectWasConsumed) { let returnedValue = newPromise._resolve(this._result); if (returnedValue !== undefined && returnedValue !== null) { if ((>returnedValue)._state !== undefined) { let returnedPromise = returnedValue as InternalPromise; newPromise._child = returnedPromise; newPromise = returnedPromise; } else { newPromise._result = (returnedValue); } } } else { newPromise._reject(this._reason); } } return newPromise; } private _resolve(value?: Nullable): Nullable> | T { try { this._state = PromiseStates.Fulfilled; this._result = value; let returnedPromise: Nullable> | T = null; if (this._onFulfilled) { returnedPromise = this._onFulfilled(value); } if (this._child) { this._child._resolve(value); } return returnedPromise; } catch(e) { this._reject((e).message); } return null; } private _reject(reason: any): void { this._state = PromiseStates.Rejected; this._reason = reason; if (this._onRejected) { this._onRejected(reason); this._rejectWasConsumed = true; } if (this._child) { if (this._rejectWasConsumed) { this._child._resolve(null); } else { this._child._reject(reason); } } } public static resolve(value: T): InternalPromise { let newPromise = new InternalPromise(); newPromise._resolve(value); return newPromise; } private static _RegisterForFulfillment(promise: InternalPromise, agregator: FulFillmentAgregator, index: number) { promise.then((value?: Nullable) => { agregator.results[index] = value; agregator.count++; if (agregator.count === agregator.target) { agregator.rootPromise._resolve(agregator.results); } return null; }, (reason: any) => { if (!agregator.rootPromise.isRejected) { agregator.rootPromise._reject(reason); } }) } public static all(promises: InternalPromise[]): InternalPromise { let newPromise = new InternalPromise(); let agregator = new FulFillmentAgregator(); agregator.target = promises.length; agregator.rootPromise = newPromise; for(var index = 0; index < promises.length; index++) { InternalPromise._RegisterForFulfillment(promises[index], agregator, index); } return newPromise; } } /** * Helper class that provides a small promise polyfill */ export class PromisePolyfill { /** * Static function used to check if the polyfill is required * If this is the case then the function will inject the polyfill to window.Promise * @param force defines a boolean used to force the injection (mostly for testing purposes) */ public static Apply(force = false): void { if (force || typeof Promise === 'undefined') { let root:any = window; root.Promise = InternalPromise; } } } }