util/promise.lua

changeset 0
550f506de75a
equal deleted inserted replaced
-1:000000000000 0:550f506de75a
1 local promise_methods = {};
2 local promise_mt = { __name = "promise", __index = promise_methods };
3
4 local xpcall = require "util.xpcall".xpcall;
5
6 function promise_mt:__tostring()
7 return "promise (" .. (self._state or "invalid") .. ")";
8 end
9
10 local function is_promise(o)
11 local mt = getmetatable(o);
12 return mt == promise_mt;
13 end
14
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
29
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
34
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
38
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
42
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 -- No need to keep references to callbacks
53 promise._pending_on_fulfilled = nil;
54 promise._pending_on_rejected = nil;
55 return true;
56 end
57
58 local function new_resolve_functions(p)
59 local resolved = false;
60 local function _resolve(v)
61 if resolved then return; end
62 resolved = true;
63 if is_promise(v) then
64 v:next(new_resolve_functions(p));
65 elseif promise_settle(p, "fulfilled", next_fulfilled, p._pending_on_fulfilled, v) then
66 p.value = v;
67 end
68
69 end
70 local function _reject(e)
71 if resolved then return; end
72 resolved = true;
73 if promise_settle(p, "rejected", next_rejected, p._pending_on_rejected, e) then
74 p.reason = e;
75 end
76 end
77 return _resolve, _reject;
78 end
79
80 local function new(f)
81 local p = setmetatable({ _state = "pending", _next = next_pending, _pending_on_fulfilled = {}, _pending_on_rejected = {} }, promise_mt);
82 if f then
83 local resolve, reject = new_resolve_functions(p);
84 local ok, ret = xpcall(f, debug.traceback, resolve, reject);
85 if not ok and p._state == "pending" then
86 reject(ret);
87 end
88 end
89 return p;
90 end
91
92 local function all(promises)
93 return new(function (resolve, reject)
94 local count, total, results = 0, #promises, {};
95 for i = 1, total do
96 promises[i]:next(function (v)
97 results[i] = v;
98 count = count + 1;
99 if count == total then
100 resolve(results);
101 end
102 end, reject);
103 end
104 end);
105 end
106
107 local function all_settled(promises)
108 return new(function (resolve)
109 local count, total, results = 0, #promises, {};
110 for i = 1, total do
111 promises[i]:next(function (v)
112 results[i] = { status = "fulfilled", value = v };
113 count = count + 1;
114 if count == total then
115 resolve(results);
116 end
117 end, function (e)
118 results[i] = { status = "rejected", reason = e };
119 count = count + 1;
120 if count == total then
121 resolve(results);
122 end
123 end);
124 end
125 end);
126 end
127
128 local function race(promises)
129 return new(function (resolve, reject)
130 for i = 1, #promises do
131 promises[i]:next(resolve, reject);
132 end
133 end);
134 end
135
136 local function resolve(v)
137 return new(function (_resolve)
138 _resolve(v);
139 end);
140 end
141
142 local function reject(v)
143 return new(function (_, _reject)
144 _reject(v);
145 end);
146 end
147
148 local function try(f)
149 return resolve():next(function () return f(); end);
150 end
151
152 function promise_methods:next(on_fulfilled, on_rejected)
153 return new(function (resolve, reject) --luacheck: ignore 431/resolve 431/reject
154 self:_next(on_fulfilled, on_rejected, resolve, reject);
155 end);
156 end
157
158 function promise_methods:catch(on_rejected)
159 return self:next(nil, on_rejected);
160 end
161
162 function promise_methods:finally(on_finally)
163 local function _on_finally(value) on_finally(); return value; end
164 local function _on_catch_finally(err) on_finally(); return reject(err); end
165 return self:next(_on_finally, _on_catch_finally);
166 end
167
168 return {
169 new = new;
170 resolve = resolve;
171 reject = reject;
172 all = all;
173 all_settled = all_settled;
174 race = race;
175 try = try;
176 is_promise = is_promise;
177 }

mercurial