Skip to content

Instantly share code, notes, and snippets.

@acutmore
Last active April 7, 2019 12:10
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save acutmore/2a8757c93bdb32b7c935ef19ecf2c5be to your computer and use it in GitHub Desktop.
Save acutmore/2a8757c93bdb32b7c935ef19ecf2c5be to your computer and use it in GitHub Desktop.
Promise Implementation where callbacks resolve in random order - To help catch race conditions
const OriginalPromise = Promise;
const enum State {
PENDING,
RESOLVED,
REJECTED
}
const emptyFn = () => {};
const FUDGE_FACTOR_MS = 100;
export class ChaosPromise<T> implements Promise<T> {
static all(vs: any[]) {
return OriginalPromise.all(vs);
}
static race(vs: any[]) {
return OriginalPromise.race(vs);
}
static resolve(v: any) {
return new ChaosPromise(resolve => resolve(v));
}
static reject(v: any) {
return new ChaosPromise((_, reject) => reject(v));
}
static monkeyPatchPrototype(originalPrototype: {then: Function}): void {
const originalThen = originalPrototype.then;
originalPrototype.then = function(resolve?: Function, reject?: Function) {
const chaosPromise = new ChaosPromise(emptyFn);
originalThen.call(this, chaosPromise.resolve, chaosPromise.reject);
return chaosPromise.then(resolve, reject);
};
}
private state = State.PENDING;
private value: T = undefined;
private callbacks: ([Function, Function, ChaosPromise<T>])[] = [];
constructor(revealed: (resolve: Function, reject: Function) => void) {
if (typeof revealed !== 'function') {
throw new TypeError(`Promise resolver ${typeof revealed} is not a function`);
}
try {
revealed(this.resolve.bind(this), this.reject.bind(this));
} catch (e) {
this.reject(e);
}
}
private resolve(v: any): void {
this.settle(State.RESOLVED, v);
}
private reject(e: any): void {
this.settle(State.REJECTED, e);
}
then(onResolve?: Function, onReject?: Function): ChaosPromise<any> {
const p = new ChaosPromise<any>(emptyFn);
if (this.state === State.PENDING) {
this.callbacks.push([onResolve, onReject, p]);
} else {
this.process(onResolve, onReject, p);
}
return p;
}
catch(onReject?: Function): ChaosPromise<any> {
return this.then(undefined, onReject);
}
finally(onSettled?: Function): ChaosPromise<any> {
return this.then(
(v: any) => {
return Promise.resolve(onSettled()).then(_ => v);
},
(e: unknown) => {
return Promise.resolve(onSettled()).then(_ => {
throw e;
});
}
);
}
done(onResolve?: Function, onReject?: Function): void {
this.then(onResolve, onReject).catch((e: unknown) => {
console.error(e);
});
}
private settle(state: State.RESOLVED | State.REJECTED, v: any): void {
if (this.state !== State.PENDING) {
return;
}
const vIsPromise = typeof v === 'object' && v != null && typeof v.then === 'function';
if (state === State.RESOLVED && vIsPromise) {
v.then(this.settle.bind(this, State.RESOLVED), this.settle.bind(this, State.REJECTED));
return;
}
this.state = state;
this.value = v;
const callbacks = this.callbacks;
this.callbacks = [];
for (const [re, rj, p] of callbacks) {
this.process(re, rj, p);
}
}
private process(
onResolve: Function | null,
onReject: Function | null,
p: ChaosPromise<any>
): void {
const callback =
this.state === State.RESOLVED
? onResolve || ((v: any) => v)
: onReject || ((e: unknown) => Promise.reject(e));
setTimeout(() => {
try {
const retVal = callback(this.value);
p.resolve(retVal);
} catch (e) {
p.reject(e);
}
}, Math.floor(Math.random() * FUDGE_FACTOR_MS));
}
}
export function monkeyPatchPrototype(originalPrototype: {then: Function}): void {
ChaosPromise.monkeyPatchPrototype(originalPrototype);
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment