-
Notifications
You must be signed in to change notification settings - Fork 2
/
promise.js
134 lines (115 loc) · 2.96 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
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
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
// possible states
const PENDING = 'PENDING'
const FULFILLED = 'FULFILLED'
const REJECTED = 'REJECTED'
class APromise {
constructor (resolver) {
// initial state
this.state = PENDING
// the fulfillment value or rejection reason is mapped internally to `value`
// initially the promise doesn't have a value
// .then handler queue
this.queue = []
// call the resolver immediately
doResolve(this, resolver)
}
then (onFulfilled, onRejected) {
// empty resolver
const promise = new APromise(() => {})
handle(this, { promise, onFulfilled, onRejected })
return promise
}
}
function handle (promise, handler) {
// take the state of the returned promise
while (promise.value instanceof APromise && promise.state !== REJECTED) {
promise = promise.value
}
if (promise.state === PENDING) {
// queue if PENDING
promise.queue.push(handler)
} else {
// execute immediately
handleResolved(promise, handler)
}
}
// call either the onFulfilled or onRejected function
function handleResolved (promise, handler) {
setImmediate(() => {
const cb = promise.state === FULFILLED ? handler.onFulfilled : handler.onRejected
if (typeof cb !== 'function') {
if (promise.state === FULFILLED) {
fulfill(handler.promise, promise.value)
} else {
reject(handler.promise, promise.value)
}
return
}
try {
const ret = cb(promise.value)
fulfill(handler.promise, ret)
} catch (err) {
reject(handler.promise, err)
}
})
}
// fulfill with `value`
function fulfill (promise, value) {
if (value === promise) {
return reject(promise, new TypeError('failed'))
}
if (value && (typeof value === 'object' || typeof value === 'function')) {
let then
try {
then = value.then
} catch (err) {
return reject(promise, err)
}
// promise
if (then === promise.then && promise instanceof APromise) {
promise.state = FULFILLED
promise.value = value
return finale(promise)
}
// thenable
if (typeof then === 'function') {
return doResolve(promise, then.bind(value))
}
}
// primitive
promise.state = FULFILLED
promise.value = value
finale(promise)
}
// reject with `reason`
function reject (promise, reason) {
promise.state = REJECTED
promise.value = reason
finale(promise)
}
function finale (promise) {
var length = promise.queue.length
for (var i = 0; i < length; i += 1) {
handle(promise, promise.queue[i])
}
}
// creates the fulfill/reject functions that are arguments of the resolver
function doResolve (promise, resolver) {
let called = false
function wrapFulfill (value) {
if (called) { return }
called = true
fulfill(promise, value)
}
function wrapReject (reason) {
if (called) { return }
called = true
reject(promise, reason)
}
try {
resolver(wrapFulfill, wrapReject)
} catch (err) {
wrapReject(err)
}
}
module.exports = APromise