MUC: Fix delay@from to be room JID (fixes #1416)
[prosody.git] / util / promise.lua
blob07c9c4dc7aeaa3a319cd238497d7c0375949ca7b
1 local promise_methods = {};
2 local promise_mt = { __name = "promise", __index = promise_methods };
4 local xpcall = require "util.xpcall".xpcall;
6 function promise_mt:__tostring()
7 return "promise (" .. (self._state or "invalid") .. ")";
8 end
10 local function is_promise(o)
11 local mt = getmetatable(o);
12 return mt == promise_mt;
13 end
15 local function wrap_handler(f, resolve, reject, default)
16 if not f then
17 return default;
18 end
19 return function (param)
20 local ok, ret = xpcall(f, debug.traceback, param);
21 if ok then
22 resolve(ret);
23 else
24 reject(ret);
25 end
26 return true;
27 end;
28 end
30 local function next_pending(self, on_fulfilled, on_rejected, resolve, reject)
31 table.insert(self._pending_on_fulfilled, wrap_handler(on_fulfilled, resolve, reject, resolve));
32 table.insert(self._pending_on_rejected, wrap_handler(on_rejected, resolve, reject, reject));
33 end
35 local function next_fulfilled(promise, on_fulfilled, on_rejected, resolve, reject) -- luacheck: ignore 212/on_rejected
36 wrap_handler(on_fulfilled, resolve, reject, resolve)(promise.value);
37 end
39 local function next_rejected(promise, on_fulfilled, on_rejected, resolve, reject) -- luacheck: ignore 212/on_fulfilled
40 wrap_handler(on_rejected, resolve, reject, reject)(promise.reason);
41 end
43 local function promise_settle(promise, new_state, new_next, cbs, value)
44 if promise._state ~= "pending" then
45 return;
46 end
47 promise._state = new_state;
48 promise._next = new_next;
49 for _, cb in ipairs(cbs) do
50 cb(value);
51 end
52 return true;
53 end
55 local function new_resolve_functions(p)
56 local resolved = false;
57 local function _resolve(v)
58 if resolved then return; end
59 resolved = true;
60 if is_promise(v) then
61 v:next(new_resolve_functions(p));
62 elseif promise_settle(p, "fulfilled", next_fulfilled, p._pending_on_fulfilled, v) then
63 p.value = v;
64 end
66 end
67 local function _reject(e)
68 if resolved then return; end
69 resolved = true;
70 if promise_settle(p, "rejected", next_rejected, p._pending_on_rejected, e) then
71 p.reason = e;
72 end
73 end
74 return _resolve, _reject;
75 end
77 local function new(f)
78 local p = setmetatable({ _state = "pending", _next = next_pending, _pending_on_fulfilled = {}, _pending_on_rejected = {} }, promise_mt);
79 if f then
80 local resolve, reject = new_resolve_functions(p);
81 local ok, ret = pcall(f, resolve, reject);
82 if not ok and p._state == "pending" then
83 reject(ret);
84 end
85 end
86 return p;
87 end
89 local function all(promises)
90 return new(function (resolve, reject)
91 local count, total, results = 0, #promises, {};
92 for i = 1, total do
93 promises[i]:next(function (v)
94 results[i] = v;
95 count = count + 1;
96 if count == total then
97 resolve(results);
98 end
99 end, reject);
101 end);
104 local function race(promises)
105 return new(function (resolve, reject)
106 for i = 1, #promises do
107 promises[i]:next(resolve, reject);
109 end);
112 local function resolve(v)
113 return new(function (_resolve)
114 _resolve(v);
115 end);
118 local function reject(v)
119 return new(function (_, _reject)
120 _reject(v);
121 end);
124 local function try(f)
125 return resolve():next(function () return f(); end);
128 function promise_methods:next(on_fulfilled, on_rejected)
129 return new(function (resolve, reject) --luacheck: ignore 431/resolve 431/reject
130 self:_next(on_fulfilled, on_rejected, resolve, reject);
131 end);
134 function promise_methods:catch(on_rejected)
135 return self:next(nil, on_rejected);
138 function promise_methods:finally(on_finally)
139 local function _on_finally(value) on_finally(); return value; end
140 local function _on_catch_finally(err) on_finally(); return reject(err); end
141 return self:next(_on_finally, _on_catch_finally);
144 return {
145 new = new;
146 resolve = resolve;
147 reject = reject;
148 all = all;
149 race = race;
150 try = try;
151 is_promise = is_promise;