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

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

mercurial