util/promise.lua
author Matthew Wild <mwild1@gmail.com>
Thu, 18 Oct 2018 12:11:50 +0100
changeset 9515 439cf3bbe5f3
parent 9513 8ef46d09386a
child 9516 4f4f9823bd1d
permissions -rw-r--r--
util.promise: Also support automatic resolution by returning a promise from an on_reject handler Originally unimplemented because it wasn't clear to me what the correct behaviour was, but the A+ spec is clear that both onFulfilled and onRejected may return a promise.
Ignore whitespace changes - Everywhere: Within whitespace: At end of lines:
9459
d54a0e129af8 util.promise: ES6-like API for promises
Matthew Wild <mwild1@gmail.com>
parents:
diff changeset
     1
local promise_methods = {};
d54a0e129af8 util.promise: ES6-like API for promises
Matthew Wild <mwild1@gmail.com>
parents:
diff changeset
     2
local promise_mt = { __name = "promise", __index = promise_methods };
d54a0e129af8 util.promise: ES6-like API for promises
Matthew Wild <mwild1@gmail.com>
parents:
diff changeset
     3
d54a0e129af8 util.promise: ES6-like API for promises
Matthew Wild <mwild1@gmail.com>
parents:
diff changeset
     4
local function is_promise(o)
d54a0e129af8 util.promise: ES6-like API for promises
Matthew Wild <mwild1@gmail.com>
parents:
diff changeset
     5
	local mt = getmetatable(o);
d54a0e129af8 util.promise: ES6-like API for promises
Matthew Wild <mwild1@gmail.com>
parents:
diff changeset
     6
	return mt == promise_mt;
d54a0e129af8 util.promise: ES6-like API for promises
Matthew Wild <mwild1@gmail.com>
parents:
diff changeset
     7
end
d54a0e129af8 util.promise: ES6-like API for promises
Matthew Wild <mwild1@gmail.com>
parents:
diff changeset
     8
d54a0e129af8 util.promise: ES6-like API for promises
Matthew Wild <mwild1@gmail.com>
parents:
diff changeset
     9
local function next_pending(self, on_fulfilled, on_rejected)
d54a0e129af8 util.promise: ES6-like API for promises
Matthew Wild <mwild1@gmail.com>
parents:
diff changeset
    10
	table.insert(self._pending_on_fulfilled, on_fulfilled);
d54a0e129af8 util.promise: ES6-like API for promises
Matthew Wild <mwild1@gmail.com>
parents:
diff changeset
    11
	table.insert(self._pending_on_rejected, on_rejected);
d54a0e129af8 util.promise: ES6-like API for promises
Matthew Wild <mwild1@gmail.com>
parents:
diff changeset
    12
end
d54a0e129af8 util.promise: ES6-like API for promises
Matthew Wild <mwild1@gmail.com>
parents:
diff changeset
    13
d54a0e129af8 util.promise: ES6-like API for promises
Matthew Wild <mwild1@gmail.com>
parents:
diff changeset
    14
local function next_fulfilled(promise, on_fulfilled, on_rejected) -- luacheck: ignore 212/on_rejected
d54a0e129af8 util.promise: ES6-like API for promises
Matthew Wild <mwild1@gmail.com>
parents:
diff changeset
    15
	on_fulfilled(promise.value);
d54a0e129af8 util.promise: ES6-like API for promises
Matthew Wild <mwild1@gmail.com>
parents:
diff changeset
    16
end
d54a0e129af8 util.promise: ES6-like API for promises
Matthew Wild <mwild1@gmail.com>
parents:
diff changeset
    17
d54a0e129af8 util.promise: ES6-like API for promises
Matthew Wild <mwild1@gmail.com>
parents:
diff changeset
    18
local function next_rejected(promise, on_fulfilled, on_rejected) -- luacheck: ignore 212/on_fulfilled
d54a0e129af8 util.promise: ES6-like API for promises
Matthew Wild <mwild1@gmail.com>
parents:
diff changeset
    19
	on_rejected(promise.reason);
d54a0e129af8 util.promise: ES6-like API for promises
Matthew Wild <mwild1@gmail.com>
parents:
diff changeset
    20
end
d54a0e129af8 util.promise: ES6-like API for promises
Matthew Wild <mwild1@gmail.com>
parents:
diff changeset
    21
d54a0e129af8 util.promise: ES6-like API for promises
Matthew Wild <mwild1@gmail.com>
parents:
diff changeset
    22
local function promise_settle(promise, new_state, new_next, cbs, value)
d54a0e129af8 util.promise: ES6-like API for promises
Matthew Wild <mwild1@gmail.com>
parents:
diff changeset
    23
	if promise._state ~= "pending" then
d54a0e129af8 util.promise: ES6-like API for promises
Matthew Wild <mwild1@gmail.com>
parents:
diff changeset
    24
		return;
d54a0e129af8 util.promise: ES6-like API for promises
Matthew Wild <mwild1@gmail.com>
parents:
diff changeset
    25
	end
d54a0e129af8 util.promise: ES6-like API for promises
Matthew Wild <mwild1@gmail.com>
parents:
diff changeset
    26
	promise._state = new_state;
d54a0e129af8 util.promise: ES6-like API for promises
Matthew Wild <mwild1@gmail.com>
parents:
diff changeset
    27
	promise._next = new_next;
d54a0e129af8 util.promise: ES6-like API for promises
Matthew Wild <mwild1@gmail.com>
parents:
diff changeset
    28
	for _, cb in ipairs(cbs) do
d54a0e129af8 util.promise: ES6-like API for promises
Matthew Wild <mwild1@gmail.com>
parents:
diff changeset
    29
		cb(value);
d54a0e129af8 util.promise: ES6-like API for promises
Matthew Wild <mwild1@gmail.com>
parents:
diff changeset
    30
	end
d54a0e129af8 util.promise: ES6-like API for promises
Matthew Wild <mwild1@gmail.com>
parents:
diff changeset
    31
	return true;
d54a0e129af8 util.promise: ES6-like API for promises
Matthew Wild <mwild1@gmail.com>
parents:
diff changeset
    32
end
d54a0e129af8 util.promise: ES6-like API for promises
Matthew Wild <mwild1@gmail.com>
parents:
diff changeset
    33
d54a0e129af8 util.promise: ES6-like API for promises
Matthew Wild <mwild1@gmail.com>
parents:
diff changeset
    34
local function new_resolve_functions(p)
d54a0e129af8 util.promise: ES6-like API for promises
Matthew Wild <mwild1@gmail.com>
parents:
diff changeset
    35
	local resolved = false;
d54a0e129af8 util.promise: ES6-like API for promises
Matthew Wild <mwild1@gmail.com>
parents:
diff changeset
    36
	local function _resolve(v)
d54a0e129af8 util.promise: ES6-like API for promises
Matthew Wild <mwild1@gmail.com>
parents:
diff changeset
    37
		if resolved then return; end
d54a0e129af8 util.promise: ES6-like API for promises
Matthew Wild <mwild1@gmail.com>
parents:
diff changeset
    38
		resolved = true;
d54a0e129af8 util.promise: ES6-like API for promises
Matthew Wild <mwild1@gmail.com>
parents:
diff changeset
    39
		if is_promise(v) then
d54a0e129af8 util.promise: ES6-like API for promises
Matthew Wild <mwild1@gmail.com>
parents:
diff changeset
    40
			v:next(new_resolve_functions(p));
d54a0e129af8 util.promise: ES6-like API for promises
Matthew Wild <mwild1@gmail.com>
parents:
diff changeset
    41
		elseif promise_settle(p, "fulfilled", next_fulfilled, p._pending_on_fulfilled, v) then
d54a0e129af8 util.promise: ES6-like API for promises
Matthew Wild <mwild1@gmail.com>
parents:
diff changeset
    42
			p.value = v;
d54a0e129af8 util.promise: ES6-like API for promises
Matthew Wild <mwild1@gmail.com>
parents:
diff changeset
    43
		end
d54a0e129af8 util.promise: ES6-like API for promises
Matthew Wild <mwild1@gmail.com>
parents:
diff changeset
    44
d54a0e129af8 util.promise: ES6-like API for promises
Matthew Wild <mwild1@gmail.com>
parents:
diff changeset
    45
	end
d54a0e129af8 util.promise: ES6-like API for promises
Matthew Wild <mwild1@gmail.com>
parents:
diff changeset
    46
	local function _reject(e)
d54a0e129af8 util.promise: ES6-like API for promises
Matthew Wild <mwild1@gmail.com>
parents:
diff changeset
    47
		if resolved then return; end
d54a0e129af8 util.promise: ES6-like API for promises
Matthew Wild <mwild1@gmail.com>
parents:
diff changeset
    48
		resolved = true;
9515
439cf3bbe5f3 util.promise: Also support automatic resolution by returning a promise from an on_reject handler
Matthew Wild <mwild1@gmail.com>
parents: 9513
diff changeset
    49
		if is_promise(e) then
439cf3bbe5f3 util.promise: Also support automatic resolution by returning a promise from an on_reject handler
Matthew Wild <mwild1@gmail.com>
parents: 9513
diff changeset
    50
			e:next(new_resolve_functions(p));
439cf3bbe5f3 util.promise: Also support automatic resolution by returning a promise from an on_reject handler
Matthew Wild <mwild1@gmail.com>
parents: 9513
diff changeset
    51
		elseif promise_settle(p, "rejected", next_rejected, p._pending_on_rejected, e) then
9459
d54a0e129af8 util.promise: ES6-like API for promises
Matthew Wild <mwild1@gmail.com>
parents:
diff changeset
    52
			p.reason = e;
d54a0e129af8 util.promise: ES6-like API for promises
Matthew Wild <mwild1@gmail.com>
parents:
diff changeset
    53
		end
d54a0e129af8 util.promise: ES6-like API for promises
Matthew Wild <mwild1@gmail.com>
parents:
diff changeset
    54
	end
d54a0e129af8 util.promise: ES6-like API for promises
Matthew Wild <mwild1@gmail.com>
parents:
diff changeset
    55
	return _resolve, _reject;
d54a0e129af8 util.promise: ES6-like API for promises
Matthew Wild <mwild1@gmail.com>
parents:
diff changeset
    56
end
d54a0e129af8 util.promise: ES6-like API for promises
Matthew Wild <mwild1@gmail.com>
parents:
diff changeset
    57
d54a0e129af8 util.promise: ES6-like API for promises
Matthew Wild <mwild1@gmail.com>
parents:
diff changeset
    58
local function new(f)
d54a0e129af8 util.promise: ES6-like API for promises
Matthew Wild <mwild1@gmail.com>
parents:
diff changeset
    59
	local p = setmetatable({ _state = "pending", _next = next_pending, _pending_on_fulfilled = {}, _pending_on_rejected = {} }, promise_mt);
d54a0e129af8 util.promise: ES6-like API for promises
Matthew Wild <mwild1@gmail.com>
parents:
diff changeset
    60
	if f then
d54a0e129af8 util.promise: ES6-like API for promises
Matthew Wild <mwild1@gmail.com>
parents:
diff changeset
    61
		local resolve, reject = new_resolve_functions(p);
d54a0e129af8 util.promise: ES6-like API for promises
Matthew Wild <mwild1@gmail.com>
parents:
diff changeset
    62
		local ok, ret = pcall(f, resolve, reject);
d54a0e129af8 util.promise: ES6-like API for promises
Matthew Wild <mwild1@gmail.com>
parents:
diff changeset
    63
		if not ok and p._state == "pending" then
d54a0e129af8 util.promise: ES6-like API for promises
Matthew Wild <mwild1@gmail.com>
parents:
diff changeset
    64
			reject(ret);
d54a0e129af8 util.promise: ES6-like API for promises
Matthew Wild <mwild1@gmail.com>
parents:
diff changeset
    65
		end
d54a0e129af8 util.promise: ES6-like API for promises
Matthew Wild <mwild1@gmail.com>
parents:
diff changeset
    66
	end
d54a0e129af8 util.promise: ES6-like API for promises
Matthew Wild <mwild1@gmail.com>
parents:
diff changeset
    67
	return p;
d54a0e129af8 util.promise: ES6-like API for promises
Matthew Wild <mwild1@gmail.com>
parents:
diff changeset
    68
end
d54a0e129af8 util.promise: ES6-like API for promises
Matthew Wild <mwild1@gmail.com>
parents:
diff changeset
    69
d54a0e129af8 util.promise: ES6-like API for promises
Matthew Wild <mwild1@gmail.com>
parents:
diff changeset
    70
local function wrap_handler(f, resolve, reject)
d54a0e129af8 util.promise: ES6-like API for promises
Matthew Wild <mwild1@gmail.com>
parents:
diff changeset
    71
	return function (param)
d54a0e129af8 util.promise: ES6-like API for promises
Matthew Wild <mwild1@gmail.com>
parents:
diff changeset
    72
		local ok, ret = pcall(f, param);
d54a0e129af8 util.promise: ES6-like API for promises
Matthew Wild <mwild1@gmail.com>
parents:
diff changeset
    73
		if ok then
d54a0e129af8 util.promise: ES6-like API for promises
Matthew Wild <mwild1@gmail.com>
parents:
diff changeset
    74
			resolve(ret);
d54a0e129af8 util.promise: ES6-like API for promises
Matthew Wild <mwild1@gmail.com>
parents:
diff changeset
    75
		else
d54a0e129af8 util.promise: ES6-like API for promises
Matthew Wild <mwild1@gmail.com>
parents:
diff changeset
    76
			reject(ret);
d54a0e129af8 util.promise: ES6-like API for promises
Matthew Wild <mwild1@gmail.com>
parents:
diff changeset
    77
		end
d54a0e129af8 util.promise: ES6-like API for promises
Matthew Wild <mwild1@gmail.com>
parents:
diff changeset
    78
	end;
d54a0e129af8 util.promise: ES6-like API for promises
Matthew Wild <mwild1@gmail.com>
parents:
diff changeset
    79
end
d54a0e129af8 util.promise: ES6-like API for promises
Matthew Wild <mwild1@gmail.com>
parents:
diff changeset
    80
d54a0e129af8 util.promise: ES6-like API for promises
Matthew Wild <mwild1@gmail.com>
parents:
diff changeset
    81
function promise_methods:next(on_fulfilled, on_rejected)
d54a0e129af8 util.promise: ES6-like API for promises
Matthew Wild <mwild1@gmail.com>
parents:
diff changeset
    82
	return new(function (resolve, reject)
d54a0e129af8 util.promise: ES6-like API for promises
Matthew Wild <mwild1@gmail.com>
parents:
diff changeset
    83
		self:_next(
d54a0e129af8 util.promise: ES6-like API for promises
Matthew Wild <mwild1@gmail.com>
parents:
diff changeset
    84
			on_fulfilled and wrap_handler(on_fulfilled, resolve, reject) or nil,
d54a0e129af8 util.promise: ES6-like API for promises
Matthew Wild <mwild1@gmail.com>
parents:
diff changeset
    85
			on_rejected and wrap_handler(on_rejected, resolve, reject) or nil
d54a0e129af8 util.promise: ES6-like API for promises
Matthew Wild <mwild1@gmail.com>
parents:
diff changeset
    86
		);
d54a0e129af8 util.promise: ES6-like API for promises
Matthew Wild <mwild1@gmail.com>
parents:
diff changeset
    87
	end);
d54a0e129af8 util.promise: ES6-like API for promises
Matthew Wild <mwild1@gmail.com>
parents:
diff changeset
    88
end
d54a0e129af8 util.promise: ES6-like API for promises
Matthew Wild <mwild1@gmail.com>
parents:
diff changeset
    89
d54a0e129af8 util.promise: ES6-like API for promises
Matthew Wild <mwild1@gmail.com>
parents:
diff changeset
    90
function promise_methods:catch(on_rejected)
d54a0e129af8 util.promise: ES6-like API for promises
Matthew Wild <mwild1@gmail.com>
parents:
diff changeset
    91
	return self:next(nil, on_rejected);
d54a0e129af8 util.promise: ES6-like API for promises
Matthew Wild <mwild1@gmail.com>
parents:
diff changeset
    92
end
d54a0e129af8 util.promise: ES6-like API for promises
Matthew Wild <mwild1@gmail.com>
parents:
diff changeset
    93
d54a0e129af8 util.promise: ES6-like API for promises
Matthew Wild <mwild1@gmail.com>
parents:
diff changeset
    94
local function all(promises)
d54a0e129af8 util.promise: ES6-like API for promises
Matthew Wild <mwild1@gmail.com>
parents:
diff changeset
    95
	return new(function (resolve, reject)
d54a0e129af8 util.promise: ES6-like API for promises
Matthew Wild <mwild1@gmail.com>
parents:
diff changeset
    96
		local count, total, results = 0, #promises, {};
d54a0e129af8 util.promise: ES6-like API for promises
Matthew Wild <mwild1@gmail.com>
parents:
diff changeset
    97
		for i = 1, total do
d54a0e129af8 util.promise: ES6-like API for promises
Matthew Wild <mwild1@gmail.com>
parents:
diff changeset
    98
			promises[i]:next(function (v)
d54a0e129af8 util.promise: ES6-like API for promises
Matthew Wild <mwild1@gmail.com>
parents:
diff changeset
    99
				results[i] = v;
d54a0e129af8 util.promise: ES6-like API for promises
Matthew Wild <mwild1@gmail.com>
parents:
diff changeset
   100
				count = count + 1;
d54a0e129af8 util.promise: ES6-like API for promises
Matthew Wild <mwild1@gmail.com>
parents:
diff changeset
   101
				if count == total then
d54a0e129af8 util.promise: ES6-like API for promises
Matthew Wild <mwild1@gmail.com>
parents:
diff changeset
   102
					resolve(results);
d54a0e129af8 util.promise: ES6-like API for promises
Matthew Wild <mwild1@gmail.com>
parents:
diff changeset
   103
				end
d54a0e129af8 util.promise: ES6-like API for promises
Matthew Wild <mwild1@gmail.com>
parents:
diff changeset
   104
			end, reject);
d54a0e129af8 util.promise: ES6-like API for promises
Matthew Wild <mwild1@gmail.com>
parents:
diff changeset
   105
		end
d54a0e129af8 util.promise: ES6-like API for promises
Matthew Wild <mwild1@gmail.com>
parents:
diff changeset
   106
	end);
d54a0e129af8 util.promise: ES6-like API for promises
Matthew Wild <mwild1@gmail.com>
parents:
diff changeset
   107
end
d54a0e129af8 util.promise: ES6-like API for promises
Matthew Wild <mwild1@gmail.com>
parents:
diff changeset
   108
d54a0e129af8 util.promise: ES6-like API for promises
Matthew Wild <mwild1@gmail.com>
parents:
diff changeset
   109
local function race(promises)
d54a0e129af8 util.promise: ES6-like API for promises
Matthew Wild <mwild1@gmail.com>
parents:
diff changeset
   110
	return new(function (resolve, reject)
d54a0e129af8 util.promise: ES6-like API for promises
Matthew Wild <mwild1@gmail.com>
parents:
diff changeset
   111
		for i = 1, #promises do
d54a0e129af8 util.promise: ES6-like API for promises
Matthew Wild <mwild1@gmail.com>
parents:
diff changeset
   112
			promises[i]:next(resolve, reject);
d54a0e129af8 util.promise: ES6-like API for promises
Matthew Wild <mwild1@gmail.com>
parents:
diff changeset
   113
		end
d54a0e129af8 util.promise: ES6-like API for promises
Matthew Wild <mwild1@gmail.com>
parents:
diff changeset
   114
	end);
d54a0e129af8 util.promise: ES6-like API for promises
Matthew Wild <mwild1@gmail.com>
parents:
diff changeset
   115
end
d54a0e129af8 util.promise: ES6-like API for promises
Matthew Wild <mwild1@gmail.com>
parents:
diff changeset
   116
d54a0e129af8 util.promise: ES6-like API for promises
Matthew Wild <mwild1@gmail.com>
parents:
diff changeset
   117
local function resolve(v)
d54a0e129af8 util.promise: ES6-like API for promises
Matthew Wild <mwild1@gmail.com>
parents:
diff changeset
   118
	return new(function (_resolve)
d54a0e129af8 util.promise: ES6-like API for promises
Matthew Wild <mwild1@gmail.com>
parents:
diff changeset
   119
		_resolve(v);
d54a0e129af8 util.promise: ES6-like API for promises
Matthew Wild <mwild1@gmail.com>
parents:
diff changeset
   120
	end);
d54a0e129af8 util.promise: ES6-like API for promises
Matthew Wild <mwild1@gmail.com>
parents:
diff changeset
   121
end
d54a0e129af8 util.promise: ES6-like API for promises
Matthew Wild <mwild1@gmail.com>
parents:
diff changeset
   122
d54a0e129af8 util.promise: ES6-like API for promises
Matthew Wild <mwild1@gmail.com>
parents:
diff changeset
   123
local function reject(v)
9513
8ef46d09386a util.promise: Fix promise.reject() to return a rejected promise, and fix buggy test for it
Matthew Wild <mwild1@gmail.com>
parents: 9459
diff changeset
   124
	return new(function (_, _reject)
9459
d54a0e129af8 util.promise: ES6-like API for promises
Matthew Wild <mwild1@gmail.com>
parents:
diff changeset
   125
		_reject(v);
d54a0e129af8 util.promise: ES6-like API for promises
Matthew Wild <mwild1@gmail.com>
parents:
diff changeset
   126
	end);
d54a0e129af8 util.promise: ES6-like API for promises
Matthew Wild <mwild1@gmail.com>
parents:
diff changeset
   127
end
d54a0e129af8 util.promise: ES6-like API for promises
Matthew Wild <mwild1@gmail.com>
parents:
diff changeset
   128
d54a0e129af8 util.promise: ES6-like API for promises
Matthew Wild <mwild1@gmail.com>
parents:
diff changeset
   129
return {
d54a0e129af8 util.promise: ES6-like API for promises
Matthew Wild <mwild1@gmail.com>
parents:
diff changeset
   130
	new = new;
d54a0e129af8 util.promise: ES6-like API for promises
Matthew Wild <mwild1@gmail.com>
parents:
diff changeset
   131
	resolve = resolve;
d54a0e129af8 util.promise: ES6-like API for promises
Matthew Wild <mwild1@gmail.com>
parents:
diff changeset
   132
	reject = reject;
d54a0e129af8 util.promise: ES6-like API for promises
Matthew Wild <mwild1@gmail.com>
parents:
diff changeset
   133
	all = all;
d54a0e129af8 util.promise: ES6-like API for promises
Matthew Wild <mwild1@gmail.com>
parents:
diff changeset
   134
	race = race;
d54a0e129af8 util.promise: ES6-like API for promises
Matthew Wild <mwild1@gmail.com>
parents:
diff changeset
   135
}