Thu, 03 Dec 2020 17:05:27 +0000
Initial commit
0 | 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 | } |