-
Notifications
You must be signed in to change notification settings - Fork 1
/
Copy pathpromise.js
86 lines (75 loc) · 2.47 KB
/
promise.js
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
const useImmediately = (value, withValue) =>
setImmediate(withValue, value) && value;
const ifFunction = f => yes => no => (typeof f === 'function' ? yes(f) : no(f));
const compose = (...fns) => initialValue =>
fns.reduce((previousResult, fn) => fn(previousResult), initialValue);
const maybeThenable = value =>
value && (typeof value === 'object' || typeof value === 'function');
const continueComputation = ({ state, value }) => handler =>
ifFunction(handler.transformInput[state])(transform => {
try {
compose(transform, transformedValue => {
if (transformedValue === handler.promise) {
throw new TypeError('A promise cannot be chained to itself.');
}
return transformedValue;
}, handler.propagateOutput.resolved)(value);
} catch (e) {
handler.propagateOutput.rejected(e);
}
})(() => handler.propagateOutput[state](value));
const spy = () => {
let called = false;
return fn => (...args) => called || ((called = true) && fn(...args));
};
const deferred = () => {
let [handlers, state, value] = [[], 'pending'];
const settle = () => {
if (state !== 'pending') {
handlers.forEach(continueComputation({ state, value }));
handlers = [];
}
};
const setStateNow = targetState => finalValue =>
useImmediately(([state, value] = [targetState, finalValue]), settle);
const setStateEventually = targetState => promise => {
const s = spy();
try {
if (maybeThenable(promise)) {
const then = promise.then;
if (typeof then === 'function') {
then.call(
promise,
s(setStateEventually('resolved')),
s(setStateNow('rejected'))
);
return;
}
}
setStateNow(targetState)(promise);
} catch (e) {
s(setStateNow('rejected'))(e);
}
};
const just = spy();
return {
resolve: just(setStateEventually('resolved')),
reject: just(setStateNow('rejected')),
promise: {
then: (resolved, rejected) =>
useImmediately(deferred(), d => {
handlers.push({
transformInput: { resolved, rejected },
propagateOutput: { resolved: d.resolve, rejected: d.reject },
promise: d.promise,
});
settle();
}).promise,
},
};
};
module.exports = {
resolved: value => useImmediately(deferred(), d => d.resolve(value)).promise,
rejected: value => useImmediately(deferred(), d => d.reject(value)).promise,
deferred,
};