Skip to content

Instantly share code, notes, and snippets.

@cevek
Created September 12, 2016 15:54
Show Gist options
  • Save cevek/b67c0d2914f02a55a6e03ffb8140f7d8 to your computer and use it in GitHub Desktop.
Save cevek/b67c0d2914f02a55a6e03ffb8140f7d8 to your computer and use it in GitHub Desktop.
type Resolve<T> = (value?: T | PromiseLike<T> | undefined) => void;
type Reject = (reason?: any) => void;
type AbortFn = ((onCancel: ()=>void)=>void) | undefined;
type OnFullfilled<T, TResult> = (value: T) => TResult | PromiseLike<TResult>;
type OnRejected<T, TResult> = (reason: any) => TResult | PromiseLike<TResult> | void;
type Executor<T> = (resolve: Resolve<T>, reject: Reject, onCancel?: AbortFn) => void
export class CancellablePromise<T>{
cancelled = false;
children?: CancellablePromise<{}>[] = undefined;
promise: Promise<T>;
onCancel?: ()=>void;
parent: CancellablePromise<{}>;
constructor(private executor: Executor<T>) {
this.children = [];
this.promise = new Promise((resolve, reject) => {
let abortFn:AbortFn = undefined;
if (executor.length >= 2) {
abortFn = onCancel => this.onCancel = onCancel;
}
executor(data => {
if (data instanceof CancellablePromise) {
let topPromise = this.findTopPromise(data);
this.addChild(topPromise);
if (this.cancelled) {
topPromise.cancel();
}
}
resolve(data);
}, reject, abortFn);
});
}
then<TResult>(onfulfilled?: OnFullfilled<T, TResult>, onrejected?: OnRejected<T, TResult>): PromiseLike<TResult> {
let resolve: Resolve<TResult>;
let reject: Reject;
let newPromise = new CancellablePromise((_resolve, _reject) => {
resolve = _resolve;
reject = _reject;
});
this.addChild(newPromise);
let _didFulfill = onfulfilled ? (data: T) => resolve(!this.cancelled ? onfulfilled(data) : undefined) : undefined;
let _didReject = onrejected ? (err: any) => reject(!this.cancelled ? onrejected(err) : undefined) : undefined;
this.promise.then(_didFulfill!, _didReject!);
return newPromise;
}
catch<TResult>(onrejected: (reason: any) => TResult | PromiseLike<TResult> | void) {
return this.then(undefined, onrejected);
}
protected findTopPromise(promise: CancellablePromise<{}>) {
let top = promise;
while (top.parent) {
top = top.parent;
}
return top;
}
protected addChild(promise: CancellablePromise<{}>) {
if (!this.children) {
this.children = [];
}
promise.parent = this;
this.children.push(promise);
}
cancelAll() {
this.findTopPromise(this).cancel();
}
cancel() {
if (this.cancelled) {
return;
}
this.cancelled = true;
if (this.onCancel) {
this.onCancel();
}
if (this.children) {
for (let i = 0; i < this.children.length; i++) {
this.children[i].cancel();
}
}
}
static resolve<T>(value?: T) {
return new CancellablePromise(resolve => resolve(value));
}
static reject<T>(error?: T) {
return new CancellablePromise((resolve, reject) => reject(error));
}
static all(array: PromiseLike<{}>[]) {
return new CancellablePromise((resolve, reject) => {
let done = 0;
let newArr = new Array(array.length);
array.forEach((promise, i) => {
if (promise && promise.then) {
done++;
promise.then(val => {
newArr[i] = val;
done--;
if (done == 0) {
resolve(newArr);
}
}, reject);
} else {
newArr[i] = promise;
}
});
});
}
static race(array: PromiseLike<{}>[]) {
return new CancellablePromise((resolve, reject) => {
for (let i = 0; i < array.length; i++) {
let promise = array[i];
if (promise && promise.then) {
promise.then(resolve, reject);
} else {
resolve(promise);
}
}
});
}
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment