/ld26

To get this branch, use:
bzr branch /bzr/ld26

« back to all changes in this revision

Viewing changes to zoetrope/core/promise.lua

  • Committer: Josh C
  • Date: 2013-04-27 16:36:06 UTC
  • Revision ID: josh@9ix.org-20130427163606-0eef0f5v9wbzoi0j
zoetrope 1.4

Show diffs side-by-side

added added

removed removed

 
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
}