bzr branch
http://9ix.org/bzr/ld27
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 |
} |