/traderous

To get this branch, use:
bzr branch /bzr/traderous
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
-- Class: Promise
-- This is a way to communicate with an asynchronous function call that
-- is modeled after the Promises/A CommonJS spec <http://wiki.commonjs.org/wiki/Promises/A>.
-- The main difference is that instead of then(), it uses andThen() as the connecting
-- method name, since 'then' is a reserved word in Lua.
--
-- A function that is asynchronous in nature can return a new Promise instance.
-- The caller can then register callbacks via the promise's andThen() method, which
-- are called when the asynchronous operation completes (in the parlance, fulfilling
-- the promise) or fails (rejecting the promise). A promise can have many callbacks attached
-- by repeatedly calling andThen() on the same promise. All callbacks will fire simultaneously.
--
-- If a promise has already failed or been fulfilled, you can still call andThen() on it.
-- If this happens, the callbacks trigger immediately. This may not be what you expect, so
-- beware.
--
-- This implementation is based heavily on RSVP.js <https://github.com/tildeio/rsvp.js>.

Promise = Class:extend
{
	-- Property: state
	-- Current state of the promise: may be 'unfulfilled', 'fulfilled', or 'failed'.
	-- This property should be considered read-only. Use resolve() or reject() to change
	-- the state of the promise.
	state = 'unfulfilled',

	-- private property: _onFulfills
	-- A table of functions to call when the promise is fulfilled.
	_onFulfills = {},
	
	-- private property: _onFails
	-- A table of functions to call when the promise is rejected.
	_onFails = {},

	-- private property: _onProgresses
	-- A function that receives calls periodically as progress is made towards completing
	-- the promise. It's up to whatever asynchronous function that owns the promise to make
	-- these calls; promises do not call this by themselves.
	_onProgresses = {},

	-- Method: fulfill
	-- Fulfills the promise, notifying all registered fulfillment handlers (e.g. via <andThen>).
	--
	-- Arguments:
	--		Multiple, will be passed to fulfillment handlers
	--
	-- Returns:
	--		nothing

	fulfill = function (self, ...)
		if STRICT then
			assert(self.state == 'unfulfilled', 'Tried to fulfill a promise whose state is ' .. (self.state or 'nil'))
		end

		self.state = 'fulfilled'
		self._fulfilledWith = {...}

		for _, func in pairs(self._onFulfills) do
			func(...)
		end
	end,
	
	-- Method: progress
	-- Notifies all registered progress handlers.
	--
	-- Arguments:
	--		Multiple, will be passed to progress handlers
	--
	-- Returns:
	--		nothing
	
	progress = function (self, ...)
		if STRICT then
			assert(self.state == 'unfulfilled', 'Tried to send progress on a promise whose state is ' .. (self.state or 'nil'))
		end

		for _, func in pairs(self._onProgresses) do
			func(...)
		end
	end,

	-- Method: fail
	-- Fails the promise, notifying all registered failure handlers (e.g. via <andThen>).
	--
	-- Arguments:
	--		errorMessage - error message, will be passed to failure handlers
	--
	-- Returns:
	--		nothing
	
	fail = function (self, errorMessage)
		if STRICT then
			assert(self.state == 'unfulfilled', 'Attempted to fail a promise whose state is ' .. (self.state or 'nil'))
		end

		self.state = 'failed'
		self._failedWith = errorMessage

		for _, func in pairs(self._onFails) do
			func(errorMessage)
		end
	end,

	-- Method: andThen
	-- Registers fulfillment, failure, and progress handlers for a promise. This can be called
	-- repeatedly to register several handlers on the same event, and all handlers are optional.
	--
	--
	-- Arguments:
	--		onFulfill - function to call when this promise is fulfiled
	--		onFail - function to call when this promise fails
	--		onProgress - function to call when this promise makes progress
	--
	-- Returns:
	--		A new promise that fulfills or fails after the passed onFulfill or onFail handlers
	--		complete. If either a onFulfill or onFail returns a promise, this new promise will
	--		not fulfill or fail until that returned promise does the same. This way, you can chain
	--		together promises.

	andThen = function (self, onFulfill, onFail, onProgress)
		if STRICT then
			local tFulfill = type(onFulfill)
			local tFail = type(onFail)
			local tProgress = type(onProgress)

			assert(tFulfill == 'function' or tFulfill == 'nil', 'Fulfilled handler for promise is ' .. tFulfill ..', not a function')
			assert(tFail == 'function' or tFail == 'nil', 'Failed handler for promise is ' .. tFail .. ', not a function')
			assert(tProgress == 'function' or tProgress == 'nil', 'Progress handler for promise is ' .. tProgress ..', not a function')
		end

		local childPromise = Promise:new()

		-- we add entries, even with nil callbacks, so that
		-- fulfillments and failures propagate up the chain

		table.insert(self._onFulfills, function (...)
			childPromise:_complete(onFulfill, 'fulfill', ...)
		end)

		table.insert(self._onFails, function (errorMessage)
			childPromise:_complete(onFail, 'fail', errorMessage)
		end)

		table.insert(self._onProgresses, onProgress)

		-- immediately trigger callbacks if we are already fulfilled or failed

		if self.state == 'fulfilled' and onFulfill then
			if self._fulfilledWith then
				childPromise:_complete(onFulfill, 'fulfill', unpack(self._fulfilledWith))
			else
				childPromise:_complete(onFulfill, 'fulfill')
			end
		end

		if self.state == 'failed' and onFail then
			childPromise:_complete(onFail, 'fail', self._failedWith)
		end

		return childPromise
	end,

	-- Method: andAlways
	-- A shortcut method that adds both fulfillment and failure handlers
	-- to a promise.
	--
	-- Arguments:
	--		func - function to call when this promise is fulfiled or failed
	--
	-- Returns:
	--		A new promise that fulfills or fails after the handler
	--		complete. If the handler returns a promise, this new promise will
	--		not fulfill or fail until that returned promise does the same.
	--		This way, you can chain together promises.

	andAlways = function (self, func)
		return self:andThen(func, func)
	end,

	-- internal method: _complete
	-- Handles fulfilling or failing a promise so that chaining works properly,
	-- and that errors are passed to the promise's fail method. 
	--
	-- arguments:
	--		callback - callback to call, can be nil
	--		defaultAction - if unsure as to whether to fulfill or fail, use this
	--		... - values to pass to the callback

	_complete = function (self, callback, defaultAction, ...)
		local results, errorMessage

		-- call the callback

		if callback then
			results = { pcall(callback, ...) }

			-- if the call succeeded, peel off that flag

			if results[1] then
				table.remove(results, 1)
			else
				errorMessage = results[2]
				results = nil
			end
		end

		-- if the callback returned a new promise, we link the current promise to it

		if results and type(results[1]) == 'table' and results[1].instanceOf and results[1]:instanceOf(Promise) then
			results[1]:andThen(function(...) self:fulfill(...) end, function(errorMessage) self:fail(errorMessage) end)

		-- if the callback returned a regular value, fulfill the promise

		elseif callback and results then
			if #results > 1 then
				self:fulfill(unpack(results))
			else
				self:fulfill(results[1])
			end

		-- if there was any kind of error, fail

		elseif errorMessage then
			self:fail(errorMessage)
			error(errorMessage)

		-- and if we did not actually have a callback, fall back to the default action
		-- (we have to simulate colon calling syntax here)

		else
			self[defaultAction](self, ...)
		end
	end
}