|
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 } |