Skip to content

Instantly share code, notes, and snippets.

@cevek
Last active September 23, 2016 16:00
Show Gist options
  • Save cevek/efb6ffae02d91ba8765e773555539d7f to your computer and use it in GitHub Desktop.
Save cevek/efb6ffae02d91ba8765e773555539d7f to your computer and use it in GitHub Desktop.
type Opt<T> = T | undefined | void | null;
type Any = any;
type This = Any;
type Arg = Any;
type PDef = P<Any>;
type Callback<R, T> = Opt<(<R>(val: T, arg: Arg) => R | P<R>)>;
const enum PromiseState{
PENDING,
RESOLVED,
REJECTED,
CANCELLED
}
export class P<T> {
value: Opt<T> = undefined;
state = PromiseState.PENDING;
children: Opt<PDef[]> = undefined;
thisArg: This;
arg: Arg;
onFulfill: Callback<{}, T>;
onReject: Callback<{}, T>;
resolve(value: T | P<T>) {
if (this.state !== PromiseState.PENDING) {
return this;
}
const newValue = this.onFulfill
? (this.thisArg ? this.onFulfill.call(this.thisArg, value, this.arg) : this.onFulfill(value as T, this.arg))
: value;
if (newValue instanceof P) {
newValue.then(this.resolveWithoutCallback, this);
newValue.catch(this.reject, this);
return this;
}
this.resolveWithoutCallback(newValue as T);
return this;
}
protected resolveWithoutCallback(value: T) {
if (this.state !== PromiseState.CANCELLED) {
this.value = value;
this.state = PromiseState.RESOLVED;
if (this.children) {
this.runChildren();
}
}
}
reject(reason: T | Error) {
if (this.state !== PromiseState.PENDING) {
return this;
}
this.value = this.onReject
? (this.thisArg ? this.onReject.call(this.thisArg, reason, this.arg) : this.onReject(reason as T, this.arg))
: reason;
this.state = PromiseState.REJECTED;
if (this.children) {
this.runChildren();
}
return this;
}
cancel() {
this.state = PromiseState.CANCELLED;
if (this.children) {
for (let i = 0; i < this.children.length; i++) {
const child = this.children[i];
child.cancel();
}
}
}
protected runChildren() {
for (let i = 0; i < this.children!.length; i++) {
const child = this.children![i];
child.resolveOrReject(this);
}
}
protected resolveOrReject(parentPromise: PDef) {
if (parentPromise.state == PromiseState.CANCELLED) {
this.cancel();
} else if (parentPromise.state == PromiseState.REJECTED && !parentPromise.onReject) {
this.reject(parentPromise.value as T);
} else {
this.resolve(parentPromise.value as T);
}
}
then<TResult>(callback: (val: T, arg: Arg) => (TResult | P<TResult>), thisArg?: This, arg?: Arg): P<TResult> {
const p = new P<TResult>();
p.onFulfill = callback as {} as Callback<T, TResult>;
p.thisArg = thisArg;
p.arg = arg;
this.addChild(p);
return p;
}
catch<TResult>(callback: (val: T, arg: Arg) => (TResult | P<TResult>), thisArg?: This, arg?: Arg): P<TResult> {
const p = new P<TResult>();
p.onReject = callback as {} as Callback<T, TResult>;
p.thisArg = thisArg;
p.arg = arg;
this.addChild(p);
return p;
}
protected addChild(promise: PDef) {
if (!this.children) {
this.children = [];
}
this.children.push(promise);
if (this.state !== PromiseState.PENDING) {
promise.resolveOrReject(this);
}
}
static resolve<R>(value: R): P<R> {
return new P<R>().resolve(value);
}
static reject<R>(reason: R): P<R> {
return new P<R>().reject(reason);
}
private static allResolve(this: PAllContext, val: {}) {
this.allCtx.arr[this.i] = val;
if (--this.allCtx.counter == 0) {
this.allCtx.promise.resolve(this.allCtx.arr);
}
}
static all<TAll>(array: (TAll | P<TAll>)[]) {
const promise = new P<Any>();
const arr = new Array(array.length);
const allCtx = {counter: 0, promise, arr};
for (let i = 0; i < array.length; i++) {
const val = array[i];
if (val instanceof P) {
allCtx.counter++;
const ctx: PAllContext = {allCtx, i};
val.then(P.allResolve, ctx);
val.catch(promise.reject, promise);
} else {
arr[i] = val;
}
}
if (!array.length) {
promise.resolve([]);
}
return promise;
}
static map(fnArray: ((val: Any)=>P<Any>)[], thisArg?: This) {
let promise = P.resolve<Any>(undefined);
for (let i = 0; i < fnArray.length; i++) {
const fn = fnArray[i];
promise = promise.then(fn, thisArg);
}
return promise;
}
static race<TAll>(array: (TAll | P<TAll>)[]) {
const promise = new P<Opt<TAll>>();
for (let i = 0; i < array.length; i++) {
const item = array[i];
if (item instanceof P) {
item.then(promise.resolve, promise);
item.catch(promise.reject, promise);
} else {
promise.resolve(item);
break;
}
}
if (array.length == 0) {
promise.resolve(undefined);
}
return promise;
}
}
interface PAllContext {
allCtx: {arr: {}[]; promise: PDef; counter: number};
i: number
}
P.prototype.thisArg = undefined;
P.prototype.arg = undefined;
P.prototype.onFulfill = undefined;
P.prototype.onReject = undefined;
// tests
import './PromiseTest';
type Opt<T> = T | undefined | void | null;
import {P} from './Promise';
function check(val: any, expected: any) {
for (let i = 0; i < Math.max(val.length, expected.length); i++) {
if (val[i] !== expected[i]) {
console.error('Test failed', i, val, expected, val[i], expected[i]);
}
}
}
function test1() {
const calls: number[] = [];
const p = new P<number>();
p.then(val => calls.push(val));
p.resolve(1);
check(calls, [1]);
}
function test2() {
const calls: number[] = [];
const p = new P<number>();
p.then(val => calls.push(val));
p.reject(1);
check(calls, []);
}
function test3() {
const calls: number[] = [];
const p = new P<number>();
p.catch(val => 2)
.then(val => calls.push(val));
p.reject(1);
check(calls, [2]);
}
function test4() {
const calls: number[] = [];
const p = new P<number>();
p.then(() => 2)
.catch(val => 3)
.then(val => calls.push(val));
p.reject(1);
check(calls, [3]);
}
function test5() {
const calls: number[] = [];
const p = new P<number>();
p.then(val => new P().resolve(val + 2))
.catch(val => 30)
.then(val => calls.push(val));
p.resolve(1);
check(calls, [3]);
}
function test6() {
const calls: number[] = [];
const p = new P<number>();
p.then(val => new P().reject(val + 10))
.catch((val: number) => {
calls.push(val);
return 7
})
.then(val => calls.push(val));
p.resolve(1);
check(calls, [11, 7]);
}
function test7() {
const calls: number[] = [];
const p = new P<number>();
p.then(val => calls.push(val));
p.then(val => {
calls.push(val + 1);
return val + 1
})
.then(val => calls.push(val));
p.resolve(1);
check(calls, [1, 2, 2]);
}
function test8() {
const calls: number[] = [];
const p = new P<number>();
const pp = p.then(val => {
const r = new P<number>().resolve(val + 1);
r.catch(a => calls.push(a + 100));
r.then(a => calls.push(a + 1));
return r;
});
pp.then(val => calls.push(val + 5));
pp.then(val => {
calls.push(val + 2);
return val + 1
})
.then(val => calls.push(val));
p.resolve(1);
check(calls, [3, 7, 4, 3]);
}
function test9() {
const calls: number[] = [];
const p = new P<number>();
p.then(val => {
const r = new P<number>();
const rr = r.then(val => new P<number>().resolve(val + 1));
r.resolve(val + 1);
return rr;
}).then(val => calls.push(val));
p.resolve(1);
check(calls, [3]);
}
function test10() {
const calls: number[] = [];
const p = new P<number>();
let pp = new P<number>();
p
.then(val => pp)
.then(val => calls.push(val));
p.resolve(1);
pp.resolve(5);
check(calls, [5]);
}
function test11() {
const calls: number[] = [];
const p = new P<number>();
p.resolve(1);
p.then(val => calls.push(val));
p.then(val => calls.push(val));
check(calls, [1, 1]);
}
function test12() {
const calls: number[] = [];
const p = new P<number>();
let pp = new P<number>();
p
.then(val => pp)
.then(val => calls.push(val)).catch(val => calls.push(val + 1));
p.resolve(1);
pp.reject(5);
check(calls, [6]);
}
function test13() {
const calls: number[] = [];
const p = new P<number>();
let pp = new P<number>();
let ppp = new P<number>();
p
.then(val => pp)
.then(val => calls.push(val));
p.resolve(1);
pp.resolve(ppp);
ppp.resolve(5);
check(calls, [5]);
}
function sleep(ms: number, val?: number) {
const p = new P<number>();
setTimeout(() => p.resolve(val!), ms);
return p;
}
function test14() {
P.all([
sleep(10).then(() => 0),
P.resolve(1),
P.resolve(2),
3,
sleep(20).then(() => 4),
]).then(arr => {
check(arr, [0, 1, 2, 3, 4]);
});
}
function test15() {
P.race([
sleep(10).then(() => 0),
P.resolve(1),
P.resolve(2),
3,
]).then(val => {
check([val], [1]);
});
}
function test16() {
const calls: number[] = [];
P.map([
() => sleep(10).then(() => {
calls.push(0);
}),
() => P.resolve(1).then(val => calls.push(1)),
() => sleep(20).then(() => {
calls.push(2);
}),
() => P.resolve(3).then(val => calls.push(3))
]).then(() => {
check(calls, [0, 1, 2, 3])
})
}
function test17() {
const calls: number[] = [];
P.resolve(1)
.then(v => {
calls.push(v);
return sleep(20, 11)
})
.then(v => {
calls.push(v);
return sleep(10, 21)
})
.then(v => {
calls.push(v);
check(calls, [1, 11, 21])
})
}
function test18() {
P.all([
sleep(10).then(() => 0),
P.resolve(1),
P.reject(2),
3,
sleep(20).then(() => 4),
]).then(arr => {
check(arr, []);
}).catch(val => {
check([val], 2);
})
}
function test19() {
P.race([
sleep(10).then(() => 0),
P.resolve(1),
P.reject(2),
3,
sleep(20).then(() => 4),
]).then(arr => {
check(arr, []);
}).catch(val => {
check([val], 2);
})
}
function test20() {
const calls: number[] = [];
const p = new P<number>();
p.then(val => calls.push(val));
p.then(val => calls.push(val)).then(val => calls.push(val));
p.cancel();
p.then(val => calls.push(val)).then(val => calls.push(val));
p.resolve(1);
check(calls, []);
}
function test21() {
const calls: number[] = [];
const p = new P<number>();
p.then(val => calls.push(val));
const pp = p.then(val => calls.push(val + 1));
pp.then(val => calls.push(val + 2));
pp.cancel();
pp.then(val => calls.push(val)).then(val => calls.push(val));
p.resolve(1);
check(calls, [1]);
}
function test22() {
const calls: number[] = [];
const p = new P<number>();
p.then(val => sleep(10, 10).then(val => calls.push(val)));
p.cancel();
p.resolve(1);
check(calls, []);
}
function test23() {
const calls: number[] = [];
const p = new P<number>();
let pp: Opt<P<number>> = undefined;
p.then(val => pp = sleep(10, 10).then(val => {calls.push(val); return 20}));
p.resolve(1);
p.cancel();
pp!.then(val => {calls.push(val); check(calls, [10, 20])});
}
test1();
test2();
test3();
test4();
test5();
test6();
test7();
test8();
test9();
test10();
test11();
test12();
test13();
test14();
test15();
test16();
test17();
test18();
test19();
test20();
test21();
test22();
test23();
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment