|
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/. */ |
|
4 |
|
5 #ifndef MERGED_COMPARTMENT |
|
6 |
|
7 this.EXPORTED_SYMBOLS = ["Async"]; |
|
8 |
|
9 const {classes: Cc, interfaces: Ci, results: Cr, utils: Cu} = Components; |
|
10 |
|
11 #endif |
|
12 |
|
13 // Constants for makeSyncCallback, waitForSyncCallback. |
|
14 const CB_READY = {}; |
|
15 const CB_COMPLETE = {}; |
|
16 const CB_FAIL = {}; |
|
17 |
|
18 const REASON_ERROR = Ci.mozIStorageStatementCallback.REASON_ERROR; |
|
19 |
|
20 Cu.import("resource://gre/modules/Services.jsm"); |
|
21 |
|
22 /* |
|
23 * Helpers for various async operations. |
|
24 */ |
|
25 this.Async = { |
|
26 |
|
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 }, |
|
56 |
|
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 */ |
|
62 |
|
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 }; |
|
73 |
|
74 // Initialize private callback data in preparation for being called. |
|
75 onComplete.state = CB_READY; |
|
76 onComplete.value = null; |
|
77 |
|
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; |
|
82 |
|
83 // Cause the caller to get an exception and stop execution. |
|
84 throw data; |
|
85 }; |
|
86 |
|
87 return onComplete; |
|
88 }, |
|
89 |
|
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; |
|
96 |
|
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 } |
|
101 |
|
102 // Reset the state of the callback to prepare for another call. |
|
103 let state = callback.state; |
|
104 callback.state = CB_READY; |
|
105 |
|
106 // Throw the value the callback decided to fail with. |
|
107 if (state == CB_FAIL) { |
|
108 throw callback.value; |
|
109 } |
|
110 |
|
111 // Return the value passed to the callback. |
|
112 return callback.value; |
|
113 }, |
|
114 |
|
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 }, |
|
129 |
|
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 }, |
|
144 |
|
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, |
|
150 |
|
151 // These are set by queryAsync. |
|
152 names: null, |
|
153 syncCb: null, |
|
154 |
|
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) { |
|
175 |
|
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; |
|
181 |
|
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 }, |
|
190 |
|
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 }; |