services/common/async.js

changeset 0
6474c204b198
     1.1 --- /dev/null	Thu Jan 01 00:00:00 1970 +0000
     1.2 +++ b/services/common/async.js	Wed Dec 31 06:09:35 2014 +0100
     1.3 @@ -0,0 +1,199 @@
     1.4 +/* This Source Code Form is subject to the terms of the Mozilla Public
     1.5 + * License, v. 2.0. If a copy of the MPL was not distributed with this file,
     1.6 + * You can obtain one at http://mozilla.org/MPL/2.0/. */
     1.7 +
     1.8 +#ifndef MERGED_COMPARTMENT
     1.9 +
    1.10 +this.EXPORTED_SYMBOLS = ["Async"];
    1.11 +
    1.12 +const {classes: Cc, interfaces: Ci, results: Cr, utils: Cu} = Components;
    1.13 +
    1.14 +#endif
    1.15 +
    1.16 +// Constants for makeSyncCallback, waitForSyncCallback.
    1.17 +const CB_READY = {};
    1.18 +const CB_COMPLETE = {};
    1.19 +const CB_FAIL = {};
    1.20 +
    1.21 +const REASON_ERROR = Ci.mozIStorageStatementCallback.REASON_ERROR;
    1.22 +
    1.23 +Cu.import("resource://gre/modules/Services.jsm");
    1.24 +
    1.25 +/*
    1.26 + * Helpers for various async operations.
    1.27 + */
    1.28 +this.Async = {
    1.29 +
    1.30 +  /**
    1.31 +   * Execute an arbitrary number of asynchronous functions one after the
    1.32 +   * other, passing the callback arguments on to the next one.  All functions
    1.33 +   * must take a callback function as their last argument.  The 'this' object
    1.34 +   * will be whatever chain()'s is.
    1.35 +   *
    1.36 +   * @usage this._chain = Async.chain;
    1.37 +   *        this._chain(this.foo, this.bar, this.baz)(args, for, foo)
    1.38 +   *
    1.39 +   * This is equivalent to:
    1.40 +   *
    1.41 +   *   let self = this;
    1.42 +   *   self.foo(args, for, foo, function (bars, args) {
    1.43 +   *     self.bar(bars, args, function (baz, params) {
    1.44 +   *       self.baz(baz, params);
    1.45 +   *     });
    1.46 +   *   });
    1.47 +   */
    1.48 +  chain: function chain() {
    1.49 +    let funcs = Array.slice(arguments);
    1.50 +    let thisObj = this;
    1.51 +    return function callback() {
    1.52 +      if (funcs.length) {
    1.53 +        let args = Array.slice(arguments).concat(callback);
    1.54 +        let f = funcs.shift();
    1.55 +        f.apply(thisObj, args);
    1.56 +      }
    1.57 +    };
    1.58 +  },
    1.59 +
    1.60 +  /**
    1.61 +   * Helpers for making asynchronous calls within a synchronous API possible.
    1.62 +   *
    1.63 +   * If you value your sanity, do not look closely at the following functions.
    1.64 +   */
    1.65 +
    1.66 +  /**
    1.67 +   * Create a sync callback that remembers state, in particular whether it has
    1.68 +   * been called.
    1.69 +   */
    1.70 +  makeSyncCallback: function makeSyncCallback() {
    1.71 +    // The main callback remembers the value it was passed, and that it got data.
    1.72 +    let onComplete = function onComplete(data) {
    1.73 +      onComplete.state = CB_COMPLETE;
    1.74 +      onComplete.value = data;
    1.75 +    };
    1.76 +
    1.77 +    // Initialize private callback data in preparation for being called.
    1.78 +    onComplete.state = CB_READY;
    1.79 +    onComplete.value = null;
    1.80 +
    1.81 +    // Allow an alternate callback to trigger an exception to be thrown.
    1.82 +    onComplete.throw = function onComplete_throw(data) {
    1.83 +      onComplete.state = CB_FAIL;
    1.84 +      onComplete.value = data;
    1.85 +
    1.86 +      // Cause the caller to get an exception and stop execution.
    1.87 +      throw data;
    1.88 +    };
    1.89 +
    1.90 +    return onComplete;
    1.91 +  },
    1.92 +
    1.93 +  /**
    1.94 +   * Wait for a sync callback to finish.
    1.95 +   */
    1.96 +  waitForSyncCallback: function waitForSyncCallback(callback) {
    1.97 +    // Grab the current thread so we can make it give up priority.
    1.98 +    let thread = Cc["@mozilla.org/thread-manager;1"].getService().currentThread;
    1.99 +
   1.100 +    // Keep waiting until our callback is triggered (unless the app is quitting).
   1.101 +    while (Async.checkAppReady() && callback.state == CB_READY) {
   1.102 +      thread.processNextEvent(true);
   1.103 +    }
   1.104 +
   1.105 +    // Reset the state of the callback to prepare for another call.
   1.106 +    let state = callback.state;
   1.107 +    callback.state = CB_READY;
   1.108 +
   1.109 +    // Throw the value the callback decided to fail with.
   1.110 +    if (state == CB_FAIL) {
   1.111 +      throw callback.value;
   1.112 +    }
   1.113 +
   1.114 +    // Return the value passed to the callback.
   1.115 +    return callback.value;
   1.116 +  },
   1.117 +
   1.118 +  /**
   1.119 +   * Check if the app is still ready (not quitting).
   1.120 +   */
   1.121 +  checkAppReady: function checkAppReady() {
   1.122 +    // Watch for app-quit notification to stop any sync calls
   1.123 +    Services.obs.addObserver(function onQuitApplication() {
   1.124 +      Services.obs.removeObserver(onQuitApplication, "quit-application");
   1.125 +      Async.checkAppReady = function() {
   1.126 +        throw Components.Exception("App. Quitting", Cr.NS_ERROR_ABORT);
   1.127 +      };
   1.128 +    }, "quit-application", false);
   1.129 +    // In the common case, checkAppReady just returns true
   1.130 +    return (Async.checkAppReady = function() { return true; })();
   1.131 +  },
   1.132 +
   1.133 +  /**
   1.134 +   * Return the two things you need to make an asynchronous call synchronous
   1.135 +   * by spinning the event loop.
   1.136 +   */
   1.137 +  makeSpinningCallback: function makeSpinningCallback() {
   1.138 +    let cb = Async.makeSyncCallback();
   1.139 +    function callback(error, ret) {
   1.140 +      if (error)
   1.141 +        cb.throw(error);
   1.142 +      cb(ret);
   1.143 +    }
   1.144 +    callback.wait = function() Async.waitForSyncCallback(cb);
   1.145 +    return callback;
   1.146 +  },
   1.147 +
   1.148 +  // Prototype for mozIStorageCallback, used in querySpinningly.
   1.149 +  // This allows us to define the handle* functions just once rather
   1.150 +  // than on every querySpinningly invocation.
   1.151 +  _storageCallbackPrototype: {
   1.152 +    results: null,
   1.153 +
   1.154 +    // These are set by queryAsync.
   1.155 +    names: null,
   1.156 +    syncCb: null,
   1.157 +
   1.158 +    handleResult: function handleResult(results) {
   1.159 +      if (!this.names) {
   1.160 +        return;
   1.161 +      }
   1.162 +      if (!this.results) {
   1.163 +        this.results = [];
   1.164 +      }
   1.165 +      let row;
   1.166 +      while ((row = results.getNextRow()) != null) {
   1.167 +        let item = {};
   1.168 +        for each (let name in this.names) {
   1.169 +          item[name] = row.getResultByName(name);
   1.170 +        }
   1.171 +        this.results.push(item);
   1.172 +      }
   1.173 +    },
   1.174 +    handleError: function handleError(error) {
   1.175 +      this.syncCb.throw(error);
   1.176 +    },
   1.177 +    handleCompletion: function handleCompletion(reason) {
   1.178 +
   1.179 +      // If we got an error, handleError will also have been called, so don't
   1.180 +      // call the callback! We never cancel statements, so we don't need to
   1.181 +      // address that quandary.
   1.182 +      if (reason == REASON_ERROR)
   1.183 +        return;
   1.184 +
   1.185 +      // If we were called with column names but didn't find any results,
   1.186 +      // the calling code probably still expects an array as a return value.
   1.187 +      if (this.names && !this.results) {
   1.188 +        this.results = [];
   1.189 +      }
   1.190 +      this.syncCb(this.results);
   1.191 +    }
   1.192 +  },
   1.193 +
   1.194 +  querySpinningly: function querySpinningly(query, names) {
   1.195 +    // 'Synchronously' asyncExecute, fetching all results by name.
   1.196 +    let storageCallback = {names: names,
   1.197 +                           syncCb: Async.makeSyncCallback()};
   1.198 +    storageCallback.__proto__ = Async._storageCallbackPrototype;
   1.199 +    query.executeAsync(storageCallback);
   1.200 +    return Async.waitForSyncCallback(storageCallback.syncCb);
   1.201 +  },
   1.202 +};

mercurial