This repository has been archived by the owner on Sep 24, 2018. It is now read-only.
-
Notifications
You must be signed in to change notification settings - Fork 31
/
promiscuous.js
138 lines (127 loc) · 5.59 KB
/
promiscuous.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
/**@license MIT-promiscuous-©Ruben Verborgh*/
(function (func, obj) {
// Type checking utility function
function is(type, item) { return (typeof item)[0] == type; }
// Creates a promise, calling callback(resolve, reject), ignoring other parameters.
function Promise(callback, handler) {
// The `handler` variable points to the function that will
// 1) handle a .then(resolved, rejected) call
// 2) handle a resolve or reject call (if the first argument === `is`)
// Before 2), `handler` holds a queue of callbacks.
// After 2), `handler` is a finalized .then handler.
handler = function pendingHandler(resolved, rejected, value, queue, then, i) {
queue = pendingHandler.q;
// Case 1) handle a .then(resolved, rejected) call
if (resolved != is) {
return Promise(function (resolve, reject) {
queue.push({ p: this, r: resolve, j: reject, 1: resolved, 0: rejected });
});
}
// Case 2) handle a resolve or reject call
// (`resolved` === `is` acts as a sentinel)
// The actual function signature is
// .re[ject|solve](<is>, success, value)
// Check if the value is a promise and try to obtain its `then` method
if (value && (is(func, value) | is(obj, value))) {
try { then = value.then; }
catch (reason) { rejected = 0; value = reason; }
}
// If the value is a promise, take over its state
if (is(func, then)) {
try { then.call(value, transferState(1), rejected = transferState(0)); }
catch (reason) { rejected(reason); }
}
// The value is not a promise; handle resolve/reject
else {
// Replace this handler with a finalized resolved/rejected handler
handler = function (Resolved, Rejected) {
// If the Resolved or Rejected parameter is not a function,
// return the original promise (now stored in the `callback` variable)
if (!is(func, (Resolved = rejected ? Resolved : Rejected)))
return callback;
// Otherwise, return a finalized promise, transforming the value with the function
return Promise(function (resolve, reject) { finalize(this, resolve, reject, value, Resolved); });
};
// Resolve/reject pending callbacks
i = 0;
while (i < queue.length) {
then = queue[i++];
// If no callback, just resolve/reject the promise
if (!is(func, resolved = then[rejected]))
(rejected ? then.r : then.j)(value);
// Otherwise, resolve/reject the promise with the result of the callback
else
finalize(then.p, then.r, then.j, value, resolved);
}
}
// Returns a function that transfers the state of the promise
function transferState(resolved) {
return function (value) { then && (then = 0, pendingHandler(is, resolved, value)); };
}
};
// The queue of pending callbacks; garbage-collected when handler is resolved/rejected
handler.q = [];
// Create and return the promise (reusing the callback variable)
callback.call(callback = { then: function (resolved, rejected) { return handler(resolved, rejected); },
"catch": function (rejected) { return handler(0, rejected); } },
function (value) { handler(is, 1, value); },
function (reason) { handler(is, 0, reason); });
return callback;
}
// Finalizes the promise by resolving/rejecting it with the transformed value
function finalize(promise, resolve, reject, value, transform) {
setImmediate(function () {
try {
// Transform the value through and check whether it's a promise
value = transform(value);
transform = value && (is(obj, value) | is(func, value)) && value.then;
// Return the result if it's not a promise
if (!is(func, transform))
resolve(value);
// If it's a promise, make sure it's not circular
else if (value == promise)
reject(TypeError());
// Take over the promise's state
else
transform.call(value, resolve, reject);
}
catch (error) { reject(error); }
});
}
// Export the main module
module.exports = Promise;
// Creates a resolved promise
Promise.resolve = ResolvedPromise;
function ResolvedPromise(value) { return Promise(function (resolve) { resolve(value); }); }
// Creates a rejected promise
Promise.reject = function (reason) { return Promise(function (resolve, reject) { reject(reason); }); };
// Transforms an array of promises into a promise for an array
Promise.all = function (promises) {
return Promise(function (resolve, reject, count, values) {
// Array of collected values
values = [];
// Resolve immediately if there are no promises
count = promises.length || resolve(values);
// Transform all elements (`map` is shorter than `forEach`)
promises.map(function (promise, index) {
ResolvedPromise(promise).then(
// Store the value and resolve if it was the last
function (value) {
values[index] = value;
--count || resolve(values);
},
// Reject if one element fails
reject);
});
});
};
// Returns a promise that resolves or rejects as soon as one promise in the array does
Promise.race = function (promises) {
return Promise(function (resolve, reject) {
// Register to all promises in the array
promises.map(function (promise) {
ResolvedPromise(promise).then(resolve, reject);
});
});
};
})('f', 'o');