LCOV - code coverage report
Current view: top level - pkg/lib/cockpit/_internal - deferred.js Coverage Total Hit
Test: cockpit Lines: 83.5 % 188 157
Test Date: 2026-07-17 12:03:54

            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          360 :         } else {
     129          363 :             deferred.reject.apply(deferred.reject, state.values);
     130          363 :         }
     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          362 : function deferred_reject(state, values) {
     169          362 :     state.values = values;
     170          362 :     state.status = 2;
     171          362 :     schedule_process_queue(state);
     172          362 : }
     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          362 :     self.reject = function reject(/* ... */) {
     204          362 :         if (state.status)
     205          362 :             return;
     206          362 :         deferred_reject(state, arguments);
     207          362 :         return self;
     208          362 :     };
     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 : }
        

Generated by: LCOV version 2.0-1