Thu, 22 Jan 2015 13:21:57 +0100
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 | }; |