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