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 369 : let later_queue = [];
9 369 : let later_timeout = null;
10 :
11 369 : function later_drain() {
12 369 : const queue = later_queue;
13 369 : later_timeout = null;
14 369 : later_queue = [];
15 369 : for (;;) {
16 369 : const func = queue.shift();
17 369 : if (!func)
18 369 : break;
19 369 : func();
20 369 : }
21 369 : }
22 :
23 369 : export function later_invoke(func) {
24 369 : if (func)
25 369 : later_queue.push(func);
26 369 : if (later_timeout === null)
27 369 : later_timeout = window.setTimeout(later_drain, 0);
28 369 : }
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 369 : function promise_then(state, fulfilled, rejected, updated) {
43 68 : if (fulfilled === undefined && rejected === undefined && updated === undefined)
44 68 : return null;
45 369 : const result = new Deferred();
46 369 : state.pending = state.pending || [];
47 369 : state.pending.push([result, fulfilled, rejected, updated]);
48 369 : if (state.status > 0)
49 369 : schedule_process_queue(state);
50 369 : return result.promise;
51 369 : }
52 :
53 369 : function create_promise(state) {
54 : /* Like jQuery the promise object is callable */
55 164 : const self = function Promise(target) {
56 16 : if (target) {
57 16 : Object.assign(target, self);
58 16 : return target;
59 16 : }
60 164 : return self;
61 164 : };
62 :
63 369 : state.status = 0;
64 :
65 369 : self.then = function then(fulfilled, rejected, updated) {
66 68 : return promise_then(state, fulfilled, rejected, updated) || self;
67 369 : };
68 :
69 367 : self.catch = function catch_(callback) {
70 66 : return promise_then(state, null, callback) || self;
71 367 : };
72 :
73 369 : self.finally = function finally_(callback, updated) {
74 369 : return promise_then(state, function() {
75 369 : return handle_callback(arguments, true, callback);
76 369 : }, function() {
77 11 : return handle_callback(arguments, false, callback);
78 2 : }, updated) || self;
79 369 : };
80 :
81 : /* Basic jQuery Promise compatibility */
82 290 : self.done = function done(fulfilled) {
83 290 : promise_then(state, fulfilled);
84 290 : return self;
85 290 : };
86 :
87 290 : self.fail = function fail(rejected) {
88 290 : promise_then(state, null, rejected);
89 290 : return self;
90 290 : };
91 :
92 367 : self.always = function always(callback) {
93 367 : promise_then(state, callback, callback);
94 367 : return self;
95 367 : };
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 369 : self.promise = self;
112 :
113 369 : return self;
114 369 : }
115 :
116 369 : function process_queue(state) {
117 369 : const pending = state.pending;
118 369 : state.process_scheduled = false;
119 369 : state.pending = undefined;
120 369 : for (let i = 0, ii = pending.length; i < ii; ++i) {
121 369 : state.pur = true;
122 369 : const deferred = pending[i][0];
123 369 : const fn = pending[i][state.status];
124 369 : if (is_function(fn)) {
125 369 : deferred.resolve(fn.apply(state.promise, state.values));
126 365 : } else if (state.status === 1) {
127 365 : deferred.resolve.apply(deferred.resolve, state.values);
128 361 : } else {
129 364 : deferred.reject.apply(deferred.reject, state.values);
130 364 : }
131 369 : }
132 369 : }
133 :
134 369 : function schedule_process_queue(state) {
135 369 : if (state.process_scheduled || !state.pending)
136 369 : return;
137 369 : state.process_scheduled = true;
138 369 : later_invoke(function() { process_queue(state) });
139 369 : }
140 :
141 369 : function deferred_resolve(state, values) {
142 369 : let then;
143 369 : let done = false;
144 369 : if (is_object(values[0]) || is_function(values[0]))
145 369 : then = values[0]?.then;
146 369 : if (is_function(then)) {
147 369 : state.status = -1;
148 369 : then.call(values[0], function(/* ... */) {
149 369 : if (done)
150 369 : return;
151 369 : done = true;
152 369 : deferred_resolve(state, arguments);
153 24 : }, function(/* ... */) {
154 24 : if (done)
155 24 : return;
156 24 : done = true;
157 24 : deferred_reject(state, arguments);
158 0 : }, function(/* ... */) {
159 0 : deferred_notify(state, arguments);
160 0 : });
161 369 : } else {
162 369 : state.values = values;
163 369 : state.status = 1;
164 369 : schedule_process_queue(state);
165 369 : }
166 369 : }
167 :
168 363 : function deferred_reject(state, values) {
169 363 : state.values = values;
170 363 : state.status = 2;
171 363 : schedule_process_queue(state);
172 363 : }
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 369 : export function Deferred() {
191 369 : const self = this;
192 369 : const state = { };
193 369 : self.promise = state.promise = create_promise(state);
194 :
195 369 : self.resolve = function resolve(/* ... */) {
196 369 : if (arguments[0] === state.promise)
197 67 : throw new Error("Expected promise to be resolved with other value than itself");
198 369 : if (!state.status)
199 369 : deferred_resolve(state, arguments);
200 369 : return self;
201 369 : };
202 :
203 363 : self.reject = function reject(/* ... */) {
204 363 : if (state.status)
205 363 : return;
206 363 : deferred_reject(state, arguments);
207 363 : return self;
208 363 : };
209 :
210 0 : self.notify = function notify(/* ... */) {
211 0 : deferred_notify(state, arguments);
212 0 : return self;
213 0 : };
214 369 : }
215 :
216 369 : function prep_promise(values, resolved) {
217 369 : const result = new Deferred();
218 369 : if (resolved)
219 76 : result.resolve.apply(result, values);
220 : else
221 76 : result.reject.apply(result, values);
222 369 : return result.promise;
223 369 : }
224 :
225 369 : function handle_callback(values, is_resolved, callback) {
226 369 : let callback_output = null;
227 369 : if (is_function(callback))
228 369 : callback_output = callback();
229 69 : if (callback_output && is_function(callback_output.then)) {
230 1 : return callback_output.then(function() {
231 1 : return prep_promise(values, is_resolved);
232 0 : }, function() {
233 0 : return prep_promise(arguments, false);
234 0 : });
235 69 : } else {
236 369 : return prep_promise(values, is_resolved);
237 369 : }
238 369 : }
|