Last active
September 23, 2016 16:00
-
-
Save cevek/efb6ffae02d91ba8765e773555539d7f to your computer and use it in GitHub Desktop.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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'; |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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