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.
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.
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
17
-- This implementation is based heavily on RSVP.js <https://github.com/tildeio/rsvp.js>.
19
Promise = Class:extend
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',
27
-- private property: _onFulfills
28
-- A table of functions to call when the promise is fulfilled.
31
-- private property: _onFails
32
-- A table of functions to call when the promise is rejected.
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.
42
-- Fulfills the promise, notifying all registered fulfillment handlers (e.g. via <andThen>).
45
-- Multiple, will be passed to fulfillment handlers
50
fulfill = function (self, ...)
52
assert(self.state == 'unfulfilled', 'Tried to fulfill a promise whose state is ' .. (self.state or 'nil'))
55
self.state = 'fulfilled'
56
self._fulfilledWith = {...}
58
for _, func in pairs(self._onFulfills) do
64
-- Notifies all registered progress handlers.
67
-- Multiple, will be passed to progress handlers
72
progress = function (self, ...)
74
assert(self.state == 'unfulfilled', 'Tried to send progress on a promise whose state is ' .. (self.state or 'nil'))
77
for _, func in pairs(self._onProgresses) do
83
-- Fails the promise, notifying all registered failure handlers (e.g. via <andThen>).
86
-- errorMessage - error message, will be passed to failure handlers
91
fail = function (self, errorMessage)
93
assert(self.state == 'unfulfilled', 'Attempted to fail a promise whose state is ' .. (self.state or 'nil'))
97
self._failedWith = errorMessage
99
for _, func in pairs(self._onFails) do
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.
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
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.
120
andThen = function (self, onFulfill, onFail, onProgress)
122
local tFulfill = type(onFulfill)
123
local tFail = type(onFail)
124
local tProgress = type(onProgress)
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')
131
local childPromise = Promise:new()
133
-- we add entries, even with nil callbacks, so that
134
-- fulfillments and failures propagate up the chain
136
table.insert(self._onFulfills, function (...)
137
childPromise:_complete(onFulfill, 'fulfill', ...)
140
table.insert(self._onFails, function (errorMessage)
141
childPromise:_complete(onFail, 'fail', errorMessage)
144
table.insert(self._onProgresses, onProgress)
146
-- immediately trigger callbacks if we are already fulfilled or failed
148
if self.state == 'fulfilled' and onFulfill then
149
if self._fulfilledWith then
150
childPromise:_complete(onFulfill, 'fulfill', unpack(self._fulfilledWith))
152
childPromise:_complete(onFulfill, 'fulfill')
156
if self.state == 'failed' and onFail then
157
childPromise:_complete(onFail, 'fail', self._failedWith)
164
-- A shortcut method that adds both fulfillment and failure handlers
168
-- func - function to call when this promise is fulfiled or failed
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.
176
andAlways = function (self, func)
177
return self:andThen(func, func)
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.
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
189
_complete = function (self, callback, defaultAction, ...)
190
local results, errorMessage
195
results = { pcall(callback, ...) }
197
-- if the call succeeded, peel off that flag
200
table.remove(results, 1)
202
errorMessage = results[2]
207
-- if the callback returned a new promise, we link the current promise to it
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)
212
-- if the callback returned a regular value, fulfill the promise
214
elseif callback and results then
216
self:fulfill(unpack(results))
218
self:fulfill(results[1])
221
-- if there was any kind of error, fail
223
elseif errorMessage then
224
self:fail(errorMessage)
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)
231
self[defaultAction](self, ...)