-
Notifications
You must be signed in to change notification settings - Fork 1
/
FireBreathPromise.js
124 lines (118 loc) · 4.49 KB
/
FireBreathPromise.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
(function() {
if (this.FireBreathPromise) { return; }
var global=this;
function isObject(obj) { var type = typeof obj; return type === 'function' || type === 'object' && !!obj; }
var toString = Object.prototype.toString;
// Shamelessly borrowed from underscore.js
var isFunction = function (obj) { return toString.call(obj) === '[object Function]'; };
// Optimize `isFunction` if appropriate. Work around an IE 11 bug. Pulled from underscore.js
if (typeof /./ !== 'function') {
isFunction = function(obj) { return typeof obj == 'function' || false; };
}
function defer(fn, arg) { setTimeout(function() { fn(arg); }, 0); }
var STATES = { PEND: 1, PROMISE: 2, RESOLVE: 3, REJECT: 4 };
function DeferredObject(name) {
var self=this;
var fulfillHandlers = [];
var rejectHandlers = [];
var state = STATES.PEND;
var value;
self.promise = {
then: function(onFulfilled, onRejected) {
var promise2 = new DeferredObject(name + "-then");
function makeCallback(cbFunc, type) {
// Return a wrapper for the provided callback function
return function handleCallback(value) {
try {
// If it's not a function, we still need to pass it through to promise2
if (isFunction(cbFunc)) {
var x = cbFunc(value);
promise2.resolve(x);
} else {
promise2[type](value);
}
} catch (e) {
// In case of exception, reject
promise2.reject(e);
}
};
}
if (state === STATES.RESOLVE) {
defer(function() { makeCallback(onFulfilled, "resolve")(value); });
} else if (state === STATES.REJECT) {
defer(function() { makeCallback(onRejected, "reject")(value); });
} else {
fulfillHandlers.push(makeCallback(onFulfilled, "resolve"));
rejectHandlers.push(makeCallback(onRejected, "reject"));
}
return promise2.promise;
},
name: name
};
function getThen(x) {
try {
return isObject(x) ? x.then : null;
} catch (e) {
self.reject(e);
return false;
}
}
self.resolve = function (x, force) {
// Check for invalid calls
if (state === STATES.RESOLVE || state === STATES.REJECT) { return; }
else if (x === self || x === self.promise) {
return self.reject(new TypeError());
} else if (state !== STATES.PEND && !force) {
return; // ignore multiple calls
}
// If getting then throws, getThen will call reject
var then = getThen(x);
if (then === false) { return; }
// If then is a function, treat it accordingly
if (isFunction(then)) {
var scope_settled = false;
var resolve = function(x) {
if (scope_settled) { return; }
scope_settled = true;
self.resolve(x, true);
}, reject = function(x) {
if (scope_settled) { return; }
scope_settled = true;
self.reject(x, true);
};
try {
state = STATES.PROMISE;
then.call(x, resolve, reject);
} catch (e) {
if (!scope_settled) {
state = STATES.PEND;
self.reject(e);
}
}
} else {
// For any other value, do a normal resolve
value = x;
state = STATES.RESOLVE;
for (var i = 0; i < fulfillHandlers.length; ++i) {
defer(fulfillHandlers[i], value);
}
}
};
self.reject = function(x) {
// Ignore the call if we're already settled, otherwise reject
if (state === STATES.RESOLVE || state === STATES.REJECT) { return; }
else if (x === self || x === self.promise) {
return self.reject(new TypeError());
}
value = x;
state = STATES.REJECT;
for (var i = 0; i < rejectHandlers.length; ++i) {
defer(rejectHandlers[i], value);
}
};
}
function makeDeferred(name) {
return new DeferredObject(name);
}
this.FireBreathPromise = makeDeferred;
}).call((typeof module == 'object' && module.exports) ? module.exports : this);