forked from nhattruongniit/learn-javascripts
-
Notifications
You must be signed in to change notification settings - Fork 0
/
Copy pathpromises-state-machine.js
119 lines (109 loc) · 3.13 KB
/
promises-state-machine.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
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
// Since a promise is just a state machine, we should start by considering the state information we will need later.
let PENDING = 0;
let FULFILLED = 1;
let REJECTED = 2;
// STATE MACHINE
function Promise(fn) {
// store state which can be pending, fulfilled or rejected
let state = PENDING;
// store value or error once FULFILLED or rejected
let value = null;
// store success & failure handler attached by calling .then or .done
let handlers = [];
// TRANSITION
// Next, lets consider the two key transitions that can occur, fulfilling and rejecting:
function fulfill(result) {
state = FULFILLED;
value = result;
handlers.forEach(handle);
handlers = null;
}
function reject(error) {
state = REJECTED;
value = error;
handlers.forEach(handle);
handlers = null;
}
// That gives us the basic low level transitions, but lets consider an extra, higher-level transition called resolve
function resolve(result) {
try {
let then = getThen(result);
if (then) {
doResolve(then.bind(result), resolve, reject);
return;
}
fulfill(result);
} catch (e) {
reject(e);
}
}
function handle(handler) {
if (state === PENDING) {
handlers.push(handler);
} else {
if (state === FULFILLED &&
typeof handler.onFulfilled === 'function') {
handler.onFulfilled(value);
}
if (state === REJECTED &&
typeof handler.onRejected === 'function') {
handler.onRejected(value);
}
}
}
this.done = function (onFulfilled, onRejected) {
// ensure we are always asynchronous
setTimeout(function () {
handle({
onFulfilled: onFulfilled,
onRejected: onRejected
});
}, 0);
}
doResolve(fn, resolve, reject);
}
/**
* Check if a value is a Promise and, if it is,
* return the `then` method of that promise.
*
* param {Promise|Any} value
* return {Function|Null}
*/
function getThen(value) {
let type = typeof value;
if (value && (type === 'object' || type === 'function')) {
let then = value.then;
if (typeof then === 'function') {
return then;
}
};
return null;
}
/**
* Take a potentially misbehaving resolver function and make sure
* onFulfilled and onRejected are only called once.
*
* Makes no guarantees about asynchrony.
*
* param {Function} fn A resolver function that may not be trusted
* param {Function} onFulfilled
* param {Function} onRejected
*/
function doResolve(fn, onFulfilled, onRejected) {
let done = false;
try {
fn(function (value) {
if (done) return;
done = true;
onFulfilled(value);
}, function (reason) {
if (done) return;
done = true;
onRejected(reason);
})
} catch (err) {
if (done) return;
done = true;
onRejected(err);
}
}