Line data Source code
1 : // SPDX-License-Identifier: LGPL-2.1-or-later
2 : import { is_function, is_object } from './common';
3 :
4 : /* ------------------------------------------------------------------------------------
5 : * An ordered queue of functions that should be called later.
6 : */
7 :
8 127 : let later_queue = [];
9 127 : let later_timeout = null;
10 :
11 127 : function later_drain() {
12 127 : const queue = later_queue;
13 127 : later_timeout = null;
14 127 : later_queue = [];
15 127 : for (;;) {
16 127 : const func = queue.shift();
17 127 : if (!func)
18 127 : break;
19 127 : func();
20 127 : }
21 127 : }
22 :
23 127 : export function later_invoke(func) {
24 127 : if (func)
25 127 : later_queue.push(func);
26 127 : if (later_timeout === null)
27 127 : later_timeout = window.setTimeout(later_drain, 0);
28 127 : }
29 :
30 : /* ------------------------------------------------------------------------------------
31 : * Promises.
32 : * Based on Q and angular promises, with some jQuery compatibility. See the angular
33 : * license in COPYING.node for license lineage. There are some key differences with
34 : * both Q and jQuery.
35 : *
36 : * * Exceptions thrown in handlers are not treated as rejections or failures.
37 : * Exceptions remain actual exceptions.
38 : * * Unlike jQuery callbacks added to an already completed promise don't execute
39 : * immediately. Wait until control is returned to the browser.
40 : */
41 :
42 127 : function promise_then(state, fulfilled, rejected, updated) {
43 20 : if (fulfilled === undefined && rejected === undefined && updated === undefined)
44 20 : return null;
45 127 : const result = new Deferred();
46 127 : state.pending = state.pending || [];
47 127 : state.pending.push([result, fulfilled, rejected, updated]);
48 127 : if (state.status > 0)
49 127 : schedule_process_queue(state);
50 127 : return result.promise;
51 127 : }
52 :
53 127 : function create_promise(state) {
54 : /* Like jQuery the promise object is callable */
55 34 : const self = function Promise(target) {
56 3 : if (target) {
57 3 : Object.assign(target, self);
58 3 : return target;
59 3 : }
60 34 : return self;
61 34 : };
62 :
63 127 : state.status = 0;
64 :
65 127 : self.then = function then(fulfilled, rejected, updated) {
66 20 : return promise_then(state, fulfilled, rejected, updated) || self;
67 127 : };
68 :
69 126 : self.catch = function catch_(callback) {
70 19 : return promise_then(state, null, callback) || self;
71 126 : };
72 :
73 127 : self.finally = function finally_(callback, updated) {
74 127 : return promise_then(state, function() {
75 127 : return handle_callback(arguments, true, callback);
76 127 : }, function() {
77 8 : return handle_callback(arguments, false, callback);
78 1 : }, updated) || self;
79 127 : };
80 :
81 : /* Basic jQuery Promise compatibility */
82 80 : self.done = function done(fulfilled) {
83 80 : promise_then(state, fulfilled);
84 80 : return self;
85 80 : };
86 :
87 80 : self.fail = function fail(rejected) {
88 80 : promise_then(state, null, rejected);
89 80 : return self;
90 80 : };
91 :
92 126 : self.always = function always(callback) {
93 126 : promise_then(state, callback, callback);
94 126 : return self;
95 126 : };
96 :
97 8 : self.progress = function progress(updated) {
98 8 : promise_then(state, null, null, updated);
99 8 : return self;
100 8 : };
101 :
102 0 : self.state = function state_() {
103 0 : if (state.status == 1)
104 0 : return "resolved";
105 0 : if (state.status == 2)
106 0 : return "rejected";
107 0 : return "pending";
108 0 : };
109 :
110 : /* Promises are recursive like jQuery */
111 127 : self.promise = self;
112 :
113 127 : return self;
114 127 : }
115 :
116 127 : function process_queue(state) {
117 127 : const pending = state.pending;
118 127 : state.process_scheduled = false;
119 127 : state.pending = undefined;
120 127 : for (let i = 0, ii = pending.length; i < ii; ++i) {
121 127 : state.pur = true;
122 127 : const deferred = pending[i][0];
123 127 : const fn = pending[i][state.status];
124 127 : if (is_function(fn)) {
125 127 : deferred.resolve(fn.apply(state.promise, state.values));
126 123 : } else if (state.status === 1) {
127 123 : deferred.resolve.apply(deferred.resolve, state.values);
128 123 : } else {
129 126 : deferred.reject.apply(deferred.reject, state.values);
130 126 : }
131 127 : }
132 127 : }
133 :
134 127 : function schedule_process_queue(state) {
135 127 : if (state.process_scheduled || !state.pending)
136 127 : return;
137 127 : state.process_scheduled = true;
138 127 : later_invoke(function() { process_queue(state) });
139 127 : }
140 :
141 127 : function deferred_resolve(state, values) {
142 127 : let then;
143 127 : let done = false;
144 127 : if (is_object(values[0]) || is_function(values[0]))
145 127 : then = values[0]?.then;
146 127 : if (is_function(then)) {
147 127 : state.status = -1;
148 127 : then.call(values[0], function(/* ... */) {
149 127 : if (done)
150 127 : return;
151 127 : done = true;
152 127 : deferred_resolve(state, arguments);
153 9 : }, function(/* ... */) {
154 9 : if (done)
155 9 : return;
156 9 : done = true;
157 9 : deferred_reject(state, arguments);
158 0 : }, function(/* ... */) {
159 0 : deferred_notify(state, arguments);
160 0 : });
161 127 : } else {
162 127 : state.values = values;
163 127 : state.status = 1;
164 127 : schedule_process_queue(state);
165 127 : }
166 127 : }
167 :
168 126 : function deferred_reject(state, values) {
169 126 : state.values = values;
170 126 : state.status = 2;
171 126 : schedule_process_queue(state);
172 126 : }
173 :
174 0 : function deferred_notify(state, values) {
175 0 : const callbacks = state.pending;
176 0 : if ((state.status <= 0) && callbacks?.length) {
177 0 : later_invoke(function() {
178 0 : for (let i = 0, ii = callbacks.length; i < ii; i++) {
179 0 : const result = callbacks[i][0];
180 0 : const callback = callbacks[i][3];
181 0 : if (is_function(callback))
182 0 : result.notify(callback.apply(state.promise, values));
183 : else
184 0 : result.notify.apply(result, values);
185 0 : }
186 0 : });
187 0 : }
188 0 : }
189 :
190 127 : export function Deferred() {
191 127 : const self = this;
192 127 : const state = { };
193 127 : self.promise = state.promise = create_promise(state);
194 :
195 127 : self.resolve = function resolve(/* ... */) {
196 127 : if (arguments[0] === state.promise)
197 19 : throw new Error("Expected promise to be resolved with other value than itself");
198 127 : if (!state.status)
199 127 : deferred_resolve(state, arguments);
200 127 : return self;
201 127 : };
202 :
203 126 : self.reject = function reject(/* ... */) {
204 126 : if (state.status)
205 126 : return;
206 126 : deferred_reject(state, arguments);
207 126 : return self;
208 126 : };
209 :
210 0 : self.notify = function notify(/* ... */) {
211 0 : deferred_notify(state, arguments);
212 0 : return self;
213 0 : };
214 127 : }
215 :
216 127 : function prep_promise(values, resolved) {
217 127 : const result = new Deferred();
218 127 : if (resolved)
219 26 : result.resolve.apply(result, values);
220 : else
221 26 : result.reject.apply(result, values);
222 127 : return result.promise;
223 127 : }
224 :
225 127 : function handle_callback(values, is_resolved, callback) {
226 127 : let callback_output = null;
227 127 : if (is_function(callback))
228 127 : callback_output = callback();
229 20 : if (callback_output && is_function(callback_output.then)) {
230 0 : return callback_output.then(function() {
231 0 : return prep_promise(values, is_resolved);
232 0 : }, function() {
233 0 : return prep_promise(arguments, false);
234 0 : });
235 20 : } else {
236 127 : return prep_promise(values, is_resolved);
237 127 : }
238 127 : }
|