/ld28

To get this branch, use:
bzr branch http://9ix.org/bzr/ld28
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
}