bzr branch
/bzr/spacey
1
by Josh C
zoetrope 1.4 |
1 |
-- Class: Promise
|
2 |
-- This is a way to communicate with an asynchronous function call that
|
|
3 |
-- is modeled after the Promises/A CommonJS spec <http://wiki.commonjs.org/wiki/Promises/A>.
|
|
4 |
-- The main difference is that instead of then(), it uses andThen() as the connecting
|
|
5 |
-- method name, since 'then' is a reserved word in Lua.
|
|
6 |
--
|
|
7 |
-- A function that is asynchronous in nature can return a new Promise instance.
|
|
8 |
-- The caller can then register callbacks via the promise's andThen() method, which
|
|
9 |
-- are called when the asynchronous operation completes (in the parlance, fulfilling
|
|
10 |
-- the promise) or fails (rejecting the promise). A promise can have many callbacks attached
|
|
11 |
-- by repeatedly calling andThen() on the same promise. All callbacks will fire simultaneously.
|
|
12 |
--
|
|
13 |
-- If a promise has already failed or been fulfilled, you can still call andThen() on it.
|
|
14 |
-- If this happens, the callbacks trigger immediately. This may not be what you expect, so
|
|
15 |
-- beware.
|
|
16 |
--
|
|
17 |
-- This implementation is based heavily on RSVP.js <https://github.com/tildeio/rsvp.js>.
|
|
18 |
||
19 |
Promise = Class:extend |
|
20 |
{
|
|
21 |
-- Property: state
|
|
22 |
-- Current state of the promise: may be 'unfulfilled', 'fulfilled', or 'failed'.
|
|
23 |
-- This property should be considered read-only. Use resolve() or reject() to change
|
|
24 |
-- the state of the promise.
|
|
25 |
state = 'unfulfilled', |
|
26 |
||
27 |
-- private property: _onFulfills
|
|
28 |
-- A table of functions to call when the promise is fulfilled.
|
|
29 |
_onFulfills = {}, |
|
30 |
||
31 |
-- private property: _onFails
|
|
32 |
-- A table of functions to call when the promise is rejected.
|
|
33 |
_onFails = {}, |
|
34 |
||
35 |
-- private property: _onProgresses
|
|
36 |
-- A function that receives calls periodically as progress is made towards completing
|
|
37 |
-- the promise. It's up to whatever asynchronous function that owns the promise to make
|
|
38 |
-- these calls; promises do not call this by themselves.
|
|
39 |
_onProgresses = {}, |
|
40 |
||
41 |
-- Method: fulfill
|
|
42 |
-- Fulfills the promise, notifying all registered fulfillment handlers (e.g. via <andThen>).
|
|
43 |
--
|
|
44 |
-- Arguments:
|
|
45 |
-- Multiple, will be passed to fulfillment handlers
|
|
46 |
--
|
|
47 |
-- Returns:
|
|
48 |
-- nothing
|
|
49 |
||
50 |
fulfill = function (self, ...) |
|
51 |
if STRICT then |
|
52 |
assert(self.state == 'unfulfilled', 'Tried to fulfill a promise whose state is ' .. (self.state or 'nil')) |
|
53 |
end
|
|
54 |
||
55 |
self.state = 'fulfilled' |
|
56 |
self._fulfilledWith = {...} |
|
57 |
||
58 |
for _, func in pairs(self._onFulfills) do |
|
59 |
func(...) |
|
60 |
end
|
|
61 |
end, |
|
62 |
||
63 |
-- Method: progress
|
|
64 |
-- Notifies all registered progress handlers.
|
|
65 |
--
|
|
66 |
-- Arguments:
|
|
67 |
-- Multiple, will be passed to progress handlers
|
|
68 |
--
|
|
69 |
-- Returns:
|
|
70 |
-- nothing
|
|
71 |
||
72 |
progress = function (self, ...) |
|
73 |
if STRICT then |
|
74 |
assert(self.state == 'unfulfilled', 'Tried to send progress on a promise whose state is ' .. (self.state or 'nil')) |
|
75 |
end
|
|
76 |
||
77 |
for _, func in pairs(self._onProgresses) do |
|
78 |
func(...) |
|
79 |
end
|
|
80 |
end, |
|
81 |
||
82 |
-- Method: fail
|
|
83 |
-- Fails the promise, notifying all registered failure handlers (e.g. via <andThen>).
|
|
84 |
--
|
|
85 |
-- Arguments:
|
|
86 |
-- errorMessage - error message, will be passed to failure handlers
|
|
87 |
--
|
|
88 |
-- Returns:
|
|
89 |
-- nothing
|
|
90 |
||
91 |
fail = function (self, errorMessage) |
|
92 |
if STRICT then |
|
93 |
assert(self.state == 'unfulfilled', 'Attempted to fail a promise whose state is ' .. (self.state or 'nil')) |
|
94 |
end
|
|
95 |
||
96 |
self.state = 'failed' |
|
97 |
self._failedWith = errorMessage |
|
98 |
||
99 |
for _, func in pairs(self._onFails) do |
|
100 |
func(errorMessage) |
|
101 |
end
|
|
102 |
end, |
|
103 |
||
104 |
-- Method: andThen
|
|
105 |
-- Registers fulfillment, failure, and progress handlers for a promise. This can be called
|
|
106 |
-- repeatedly to register several handlers on the same event, and all handlers are optional.
|
|
107 |
--
|
|
108 |
--
|
|
109 |
-- Arguments:
|
|
110 |
-- onFulfill - function to call when this promise is fulfiled
|
|
111 |
-- onFail - function to call when this promise fails
|
|
112 |
-- onProgress - function to call when this promise makes progress
|
|
113 |
--
|
|
114 |
-- Returns:
|
|
115 |
-- A new promise that fulfills or fails after the passed onFulfill or onFail handlers
|
|
116 |
-- complete. If either a onFulfill or onFail returns a promise, this new promise will
|
|
117 |
-- not fulfill or fail until that returned promise does the same. This way, you can chain
|
|
118 |
-- together promises.
|
|
119 |
||
120 |
andThen = function (self, onFulfill, onFail, onProgress) |
|
121 |
if STRICT then |
|
122 |
local tFulfill = type(onFulfill) |
|
123 |
local tFail = type(onFail) |
|
124 |
local tProgress = type(onProgress) |
|
125 |
||
126 |
assert(tFulfill == 'function' or tFulfill == 'nil', 'Fulfilled handler for promise is ' .. tFulfill ..', not a function') |
|
127 |
assert(tFail == 'function' or tFail == 'nil', 'Failed handler for promise is ' .. tFail .. ', not a function') |
|
128 |
assert(tProgress == 'function' or tProgress == 'nil', 'Progress handler for promise is ' .. tProgress ..', not a function') |
|
129 |
end
|
|
130 |
||
131 |
local childPromise = Promise:new() |
|
132 |
||
133 |
-- we add entries, even with nil callbacks, so that
|
|
134 |
-- fulfillments and failures propagate up the chain
|
|
135 |
||
136 |
table.insert(self._onFulfills, function (...) |
|
137 |
childPromise:_complete(onFulfill, 'fulfill', ...) |
|
138 |
end) |
|
139 |
||
140 |
table.insert(self._onFails, function (errorMessage) |
|
141 |
childPromise:_complete(onFail, 'fail', errorMessage) |
|
142 |
end) |
|
143 |
||
144 |
table.insert(self._onProgresses, onProgress) |
|
145 |
||
146 |
-- immediately trigger callbacks if we are already fulfilled or failed
|
|
147 |
||
148 |
if self.state == 'fulfilled' and onFulfill then |
|
149 |
if self._fulfilledWith then |
|
150 |
childPromise:_complete(onFulfill, 'fulfill', unpack(self._fulfilledWith)) |
|
151 |
else
|
|
152 |
childPromise:_complete(onFulfill, 'fulfill') |
|
153 |
end
|
|
154 |
end
|
|
155 |
||
156 |
if self.state == 'failed' and onFail then |
|
157 |
childPromise:_complete(onFail, 'fail', self._failedWith) |
|
158 |
end
|
|
159 |
||
160 |
return childPromise |
|
161 |
end, |
|
162 |
||
163 |
-- Method: andAlways
|
|
164 |
-- A shortcut method that adds both fulfillment and failure handlers
|
|
165 |
-- to a promise.
|
|
166 |
--
|
|
167 |
-- Arguments:
|
|
168 |
-- func - function to call when this promise is fulfiled or failed
|
|
169 |
--
|
|
170 |
-- Returns:
|
|
171 |
-- A new promise that fulfills or fails after the handler
|
|
172 |
-- complete. If the handler returns a promise, this new promise will
|
|
173 |
-- not fulfill or fail until that returned promise does the same.
|
|
174 |
-- This way, you can chain together promises.
|
|
175 |
||
176 |
andAlways = function (self, func) |
|
177 |
return self:andThen(func, func) |
|
178 |
end, |
|
179 |
||
180 |
-- internal method: _complete
|
|
181 |
-- Handles fulfilling or failing a promise so that chaining works properly,
|
|
182 |
-- and that errors are passed to the promise's fail method.
|
|
183 |
--
|
|
184 |
-- arguments:
|
|
185 |
-- callback - callback to call, can be nil
|
|
186 |
-- defaultAction - if unsure as to whether to fulfill or fail, use this
|
|
187 |
-- ... - values to pass to the callback
|
|
188 |
||
189 |
_complete = function (self, callback, defaultAction, ...) |
|
190 |
local results, errorMessage |
|
191 |
||
192 |
-- call the callback
|
|
193 |
||
194 |
if callback then |
|
195 |
results = { pcall(callback, ...) } |
|
196 |
||
197 |
-- if the call succeeded, peel off that flag
|
|
198 |
||
199 |
if results[1] then |
|
200 |
table.remove(results, 1) |
|
201 |
else
|
|
202 |
errorMessage = results[2] |
|
203 |
results = nil |
|
204 |
end
|
|
205 |
end
|
|
206 |
||
207 |
-- if the callback returned a new promise, we link the current promise to it
|
|
208 |
||
209 |
if results and type(results[1]) == 'table' and results[1].instanceOf and results[1]:instanceOf(Promise) then |
|
210 |
results[1]:andThen(function(...) self:fulfill(...) end, function(errorMessage) self:fail(errorMessage) end) |
|
211 |
||
212 |
-- if the callback returned a regular value, fulfill the promise
|
|
213 |
||
214 |
elseif callback and results then |
|
215 |
if #results > 1 then |
|
216 |
self:fulfill(unpack(results)) |
|
217 |
else
|
|
218 |
self:fulfill(results[1]) |
|
219 |
end
|
|
220 |
||
221 |
-- if there was any kind of error, fail
|
|
222 |
||
223 |
elseif errorMessage then |
|
224 |
self:fail(errorMessage) |
|
225 |
error(errorMessage) |
|
226 |
||
227 |
-- and if we did not actually have a callback, fall back to the default action
|
|
228 |
-- (we have to simulate colon calling syntax here)
|
|
229 |
||
230 |
else
|
|
231 |
self[defaultAction](self, ...) |
|
232 |
end
|
|
233 |
end
|
|
234 |
}
|