services/common/async.js

Thu, 22 Jan 2015 13:21:57 +0100

author
Michael Schloh von Bennewitz <michael@schloh.com>
date
Thu, 22 Jan 2015 13:21:57 +0100
branch
TOR_BUG_9701
changeset 15
b8a032363ba2
permissions
-rw-r--r--

Incorporate requested changes from Mozilla in review:
https://bugzilla.mozilla.org/show_bug.cgi?id=1123480#c6

     1 /* This Source Code Form is subject to the terms of the Mozilla Public
     2  * License, v. 2.0. If a copy of the MPL was not distributed with this file,
     3  * You can obtain one at http://mozilla.org/MPL/2.0/. */
     5 #ifndef MERGED_COMPARTMENT
     7 this.EXPORTED_SYMBOLS = ["Async"];
     9 const {classes: Cc, interfaces: Ci, results: Cr, utils: Cu} = Components;
    11 #endif
    13 // Constants for makeSyncCallback, waitForSyncCallback.
    14 const CB_READY = {};
    15 const CB_COMPLETE = {};
    16 const CB_FAIL = {};
    18 const REASON_ERROR = Ci.mozIStorageStatementCallback.REASON_ERROR;
    20 Cu.import("resource://gre/modules/Services.jsm");
    22 /*
    23  * Helpers for various async operations.
    24  */
    25 this.Async = {
    27   /**
    28    * Execute an arbitrary number of asynchronous functions one after the
    29    * other, passing the callback arguments on to the next one.  All functions
    30    * must take a callback function as their last argument.  The 'this' object
    31    * will be whatever chain()'s is.
    32    *
    33    * @usage this._chain = Async.chain;
    34    *        this._chain(this.foo, this.bar, this.baz)(args, for, foo)
    35    *
    36    * This is equivalent to:
    37    *
    38    *   let self = this;
    39    *   self.foo(args, for, foo, function (bars, args) {
    40    *     self.bar(bars, args, function (baz, params) {
    41    *       self.baz(baz, params);
    42    *     });
    43    *   });
    44    */
    45   chain: function chain() {
    46     let funcs = Array.slice(arguments);
    47     let thisObj = this;
    48     return function callback() {
    49       if (funcs.length) {
    50         let args = Array.slice(arguments).concat(callback);
    51         let f = funcs.shift();
    52         f.apply(thisObj, args);
    53       }
    54     };
    55   },
    57   /**
    58    * Helpers for making asynchronous calls within a synchronous API possible.
    59    *
    60    * If you value your sanity, do not look closely at the following functions.
    61    */
    63   /**
    64    * Create a sync callback that remembers state, in particular whether it has
    65    * been called.
    66    */
    67   makeSyncCallback: function makeSyncCallback() {
    68     // The main callback remembers the value it was passed, and that it got data.
    69     let onComplete = function onComplete(data) {
    70       onComplete.state = CB_COMPLETE;
    71       onComplete.value = data;
    72     };
    74     // Initialize private callback data in preparation for being called.
    75     onComplete.state = CB_READY;
    76     onComplete.value = null;
    78     // Allow an alternate callback to trigger an exception to be thrown.
    79     onComplete.throw = function onComplete_throw(data) {
    80       onComplete.state = CB_FAIL;
    81       onComplete.value = data;
    83       // Cause the caller to get an exception and stop execution.
    84       throw data;
    85     };
    87     return onComplete;
    88   },
    90   /**
    91    * Wait for a sync callback to finish.
    92    */
    93   waitForSyncCallback: function waitForSyncCallback(callback) {
    94     // Grab the current thread so we can make it give up priority.
    95     let thread = Cc["@mozilla.org/thread-manager;1"].getService().currentThread;
    97     // Keep waiting until our callback is triggered (unless the app is quitting).
    98     while (Async.checkAppReady() && callback.state == CB_READY) {
    99       thread.processNextEvent(true);
   100     }
   102     // Reset the state of the callback to prepare for another call.
   103     let state = callback.state;
   104     callback.state = CB_READY;
   106     // Throw the value the callback decided to fail with.
   107     if (state == CB_FAIL) {
   108       throw callback.value;
   109     }
   111     // Return the value passed to the callback.
   112     return callback.value;
   113   },
   115   /**
   116    * Check if the app is still ready (not quitting).
   117    */
   118   checkAppReady: function checkAppReady() {
   119     // Watch for app-quit notification to stop any sync calls
   120     Services.obs.addObserver(function onQuitApplication() {
   121       Services.obs.removeObserver(onQuitApplication, "quit-application");
   122       Async.checkAppReady = function() {
   123         throw Components.Exception("App. Quitting", Cr.NS_ERROR_ABORT);
   124       };
   125     }, "quit-application", false);
   126     // In the common case, checkAppReady just returns true
   127     return (Async.checkAppReady = function() { return true; })();
   128   },
   130   /**
   131    * Return the two things you need to make an asynchronous call synchronous
   132    * by spinning the event loop.
   133    */
   134   makeSpinningCallback: function makeSpinningCallback() {
   135     let cb = Async.makeSyncCallback();
   136     function callback(error, ret) {
   137       if (error)
   138         cb.throw(error);
   139       cb(ret);
   140     }
   141     callback.wait = function() Async.waitForSyncCallback(cb);
   142     return callback;
   143   },
   145   // Prototype for mozIStorageCallback, used in querySpinningly.
   146   // This allows us to define the handle* functions just once rather
   147   // than on every querySpinningly invocation.
   148   _storageCallbackPrototype: {
   149     results: null,
   151     // These are set by queryAsync.
   152     names: null,
   153     syncCb: null,
   155     handleResult: function handleResult(results) {
   156       if (!this.names) {
   157         return;
   158       }
   159       if (!this.results) {
   160         this.results = [];
   161       }
   162       let row;
   163       while ((row = results.getNextRow()) != null) {
   164         let item = {};
   165         for each (let name in this.names) {
   166           item[name] = row.getResultByName(name);
   167         }
   168         this.results.push(item);
   169       }
   170     },
   171     handleError: function handleError(error) {
   172       this.syncCb.throw(error);
   173     },
   174     handleCompletion: function handleCompletion(reason) {
   176       // If we got an error, handleError will also have been called, so don't
   177       // call the callback! We never cancel statements, so we don't need to
   178       // address that quandary.
   179       if (reason == REASON_ERROR)
   180         return;
   182       // If we were called with column names but didn't find any results,
   183       // the calling code probably still expects an array as a return value.
   184       if (this.names && !this.results) {
   185         this.results = [];
   186       }
   187       this.syncCb(this.results);
   188     }
   189   },
   191   querySpinningly: function querySpinningly(query, names) {
   192     // 'Synchronously' asyncExecute, fetching all results by name.
   193     let storageCallback = {names: names,
   194                            syncCb: Async.makeSyncCallback()};
   195     storageCallback.__proto__ = Async._storageCallbackPrototype;
   196     query.executeAsync(storageCallback);
   197     return Async.waitForSyncCallback(storageCallback.syncCb);
   198   },
   199 };

mercurial