Changeset

9549:800c274928bf

util.promise: Ensure chained promises always receive a value/rejection even if an intermediate promise has no handlers
author Matthew Wild <mwild1@gmail.com>
date Thu, 25 Oct 2018 14:38:00 +0100
parents 9548:a83afc22e9d7
children 9550:98de4c2e2627
files spec/util_promise_spec.lua util/promise.lua
diffstat 2 files changed, 60 insertions(+), 22 deletions(-) [+]
line wrap: on
line diff
--- a/spec/util_promise_spec.lua	Thu Oct 25 14:19:27 2018 +0100
+++ b/spec/util_promise_spec.lua	Thu Oct 25 14:38:00 2018 +0100
@@ -138,6 +138,43 @@
 		assert.equal("ok", result);
 	end);
 
+	it("propagates errors down the chain, even when some handlers are not provided", function ()
+		local r, result;
+		local test_error = {};
+		local p = promise.new(function (resolve, reject)
+			r = resolve;
+		end);
+		local cb = spy.new(function () end);
+		local err_cb = spy.new(function (e) result = e end);
+		local p2 = p:next(function () error(test_error) end);
+		local p3 = p2:next(cb)
+		p3:catch(err_cb);
+		assert.spy(cb).was_called(0);
+		assert.spy(err_cb).was_called(0);
+		r("oh doh");
+		assert.spy(cb).was_called(0);
+		assert.spy(err_cb).was_called(1);
+		assert.equal(test_error, result);
+	end);
+
+	it("propagates values down the chain, even when some handlers are not provided", function ()
+		local r;
+		local p = promise.new(function (resolve, reject)
+			r = resolve;
+		end);
+		local cb = spy.new(function () end);
+		local err_cb = spy.new(function () end);
+		local p2 = p:next(function (v) return v; end);
+		local p3 = p2:catch(err_cb)
+		p3:next(cb);
+		assert.spy(cb).was_called(0);
+		assert.spy(err_cb).was_called(0);
+		r(1337);
+		assert.spy(cb).was_called(1);
+		assert.spy(cb).was_called_with(1337);
+		assert.spy(err_cb).was_called(0);
+	end);
+
 	describe("race()", function ()
 		it("works with fulfilled promises", function ()
 			local p1, p2 = promise.resolve("yep"), promise.resolve("nope");
--- a/util/promise.lua	Thu Oct 25 14:19:27 2018 +0100
+++ b/util/promise.lua	Thu Oct 25 14:38:00 2018 +0100
@@ -10,17 +10,32 @@
 	return mt == promise_mt;
 end
 
-local function next_pending(self, on_fulfilled, on_rejected)
-	table.insert(self._pending_on_fulfilled, on_fulfilled);
-	table.insert(self._pending_on_rejected, on_rejected);
+local function wrap_handler(f, resolve, reject, default)
+	if not f then
+		return default;
+	end
+	return function (param)
+		local ok, ret = pcall(f, param);
+		if ok then
+			resolve(ret);
+		else
+			reject(ret);
+		end
+		return true;
+	end;
 end
 
-local function next_fulfilled(promise, on_fulfilled, on_rejected) -- luacheck: ignore 212/on_rejected
-	on_fulfilled(promise.value);
+local function next_pending(self, on_fulfilled, on_rejected, resolve, reject)
+	table.insert(self._pending_on_fulfilled, wrap_handler(on_fulfilled, resolve, reject, resolve));
+	table.insert(self._pending_on_rejected, wrap_handler(on_rejected, resolve, reject, reject));
 end
 
-local function next_rejected(promise, on_fulfilled, on_rejected) -- luacheck: ignore 212/on_fulfilled
-	on_rejected(promise.reason);
+local function next_fulfilled(promise, on_fulfilled, on_rejected, resolve, reject) -- luacheck: ignore 212/on_rejected
+	wrap_handler(on_fulfilled, resolve, reject)(promise.value);
+end
+
+local function next_rejected(promise, on_fulfilled, on_rejected, resolve, reject) -- luacheck: ignore 212/on_fulfilled
+	wrap_handler(on_rejected, resolve, reject)(promise.reason);
 end
 
 local function promise_settle(promise, new_state, new_next, cbs, value)
@@ -59,17 +74,6 @@
 	return _resolve, _reject;
 end
 
-local function wrap_handler(f, resolve, reject)
-	return function (param)
-		local ok, ret = pcall(f, param);
-		if ok then
-			resolve(ret);
-		else
-			reject(ret);
-		end
-	end;
-end
-
 local function new(f)
 	local p = setmetatable({ _state = "pending", _next = next_pending, _pending_on_fulfilled = {}, _pending_on_rejected = {} }, promise_mt);
 	if f then
@@ -123,10 +127,7 @@
 
 function promise_methods:next(on_fulfilled, on_rejected)
 	return new(function (resolve, reject) --luacheck: ignore 431/resolve 431/reject
-		self:_next(
-			on_fulfilled and wrap_handler(on_fulfilled, resolve, reject) or nil,
-			on_rejected and wrap_handler(on_rejected, resolve, reject) or nil
-		);
+		self:_next(on_fulfilled, on_rejected, resolve, reject);
 	end);
 end