forked from Zolmeister/promiz
-
Notifications
You must be signed in to change notification settings - Fork 0
/
promiz.js
282 lines (229 loc) · 7.08 KB
/
promiz.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
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
!function(){
// This object gets globalalized/exported
var promiz = {
// promise factory
defer: function(){
return new defer()
},
// calls a function and resolved as a promise
fcall: function() {
var def = new defer()
var args = Array.apply([], arguments)
var fn = args.shift()
try {
var val = fn.apply(null, args)
def.resolve(val)
} catch(e) {
def.reject(e)
}
return def
},
// calls a node-style function (eg. expects callback as function(err, callback))
nfcall: function() {
var def = new defer()
var args = Array.apply([], arguments)
var fn = args.shift()
try {
// Add our custom promise callback to the end of the arguments
args.push(function(err, val){
if(err) {
return def.reject(err)
}
return def.resolve(val)
})
fn.apply(null, args)
} catch (e) {
def.reject(e)
}
return def
}
}
// This is the promise object itself
function defer(){
this.promise = this; // make compatible with when.js defer.promise. NF 12/08/2013
// State transitions from pending to either resolved or rejected
this.state = 'pending'
// Our current value
this.val = null
// The current stack of deferred calls that need to be made
this.stack = []
// If there is an unhandled exception durring stack execution, should we throw it?
this.failing = false
// Resolved the promise to a value. Only affects the first time it is called
this.resolve = function(val){
if (this.state === 'pending'){
this.state = 'resolved'
this.fire(val)
}
return this
}
// Rejects the promise with a value. Only affects the first time it is called
this.reject = function(val){
if (this.state === 'pending'){
this.state = 'rejected'
this.fire(val)
}
return this
}
// The heart of the promise, adding a defered call to our call stack
this.then = function(fn, er){
this.stack.push([fn, er])
if (this.state !== 'pending') {
this.fire()
}
return this
}
// If there is an unhandled error, throw it
// End the promise chain by returning null
this.done = function(){
this.failing = true
if (this.state !== 'pending') {
this.fire()
}
return null
}
// Catch any errors up to this point
this.fail = function (fn) {
return this.then(null, fn)
}
// Allow for node-style callback returning
this.nodeify = function (cb) {
// Process asyncronously, so if the function fails the error gets thrown
function tick(fn){
if(typeof process !== 'undefined' && process.nextTick) {
process.nextTick(fn)
} else {
setTimeout(fn, 0)
}
}
if(cb) {
this.then(function(val){
tick(function(){
cb(null, val)
})
return val
}, function(err){
tick(function(){
cb(err)
})
})
}
// still returns a promise to allow `dual` functions (callback + promise)
return this
}
// Apply a list value over the next function
this.spread = function (fn, er) {
return this.all().then(function(list){
return fn ? fn.apply(null, list) : null
}, er)
}
// Resolves an array of promises before continuing
this.all = function () {
var self = this
// create a new deferred, to be resolved when we finish
var def = new defer()
// Add a special function to the stack, which takes in the list of promise objects
this.stack.push([function(list){
list = list ? (list instanceof Array ? list : [list]) : []
if (list.length === 0){
return list
}
// We count up resolved and match it to the length of the list of promises
// This lets us know when we've finished
var cnt = 0
var len = list.length
function checkDone(){
if(cnt !== len) {
return
}
def.resolve(list)
}
// iterate over the list, resolving each value
var ind = len
while(ind--) {
// Create varaible scope
(function(){
var i = ind
var val = list[i]
if(val && val.then){
val.then(function(res){
list[i] = res
cnt++
checkDone()
}, function(err){
def.reject(err)
})
} else {
list[i] = val
cnt++
checkDone()
}
})()
}
return null
}, null])
if (this.state !== 'pending') {
this.fire()
}
return def
}
// This is our main execution thread
// Here is where we consume the stack of promises
this.fire = function (val) {
var self = this
this.val = typeof val !== 'undefined' ? val : this.val
// Iterate through the stack
while(this.stack.length && this.state !== 'pending') {
// Get the next stack item
var entry = this.stack.shift()
var fn = this.state === 'rejected' ? entry[1] : entry[0]
if(fn) {
try {
this.val = fn.call(null, this.val)
// If the value returned is a promise, resolve it
if(this.val && typeof this.val.then === 'function') {
var prevState = this.state
// Halt stack execution until the promise resolves
this.state = 'pending'
// resolving
this.val.then(function(v){
// success callback
self.resolve(v)
}, function(err){
// error callback
// re-run the stack item if it has an error callback
// but only if we weren't already in a rejected state
if(prevState !== 'rejected' && entry[1]) {
self.stack.unshift(entry)
}
self.reject(err)
})
} else {
this.state = 'resolved'
}
} catch (e) {
// the function call failed, lets reject ourselves
// and re-run the stack item in case it can handle an error case
// but only if we didn't just do that (eg. the error function of on the stack threw)
this.val = e
if(this.state !== 'rejected' && entry[1]) {
this.stack.unshift(entry)
}
this.state = 'rejected'
}
}
}
// If the `failing` flag has been set, and we have exausted the stack, and we have an error
// Throw the error
if(this.failing && this.stack.length === 0 && this.state === 'rejected') {
throw this.val
}
}
}
// Export our library object, either for node.js or as a globally scoped variable
if(typeof module !== 'undefined' && module.exports) {
module.exports = promiz
} else {
this.Promiz = promiz
}
}()