Sat, 03 Jan 2015 20:18:00 +0100
Conditionally enable double key logic according to:
private browsing mode or privacy.thirdparty.isolate preference and
implement in GetCookieStringCommon and FindCookie where it counts...
With some reservations of how to convince FindCookie users to test
condition and pass a nullptr when disabling double key logic.
michael@0 | 1 | /* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ |
michael@0 | 2 | /* vim: set ts=2 et sw=2 tw=80 filetype=javascript: */ |
michael@0 | 3 | /* This Source Code Form is subject to the terms of the Mozilla Public |
michael@0 | 4 | * License, v. 2.0. If a copy of the MPL was not distributed with this |
michael@0 | 5 | * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ |
michael@0 | 6 | |
michael@0 | 7 | "use strict"; |
michael@0 | 8 | |
michael@0 | 9 | /** |
michael@0 | 10 | * This implementation file is imported by the Promise.jsm module, and as a |
michael@0 | 11 | * special case by the debugger server. To support chrome debugging, the |
michael@0 | 12 | * debugger server needs to have all its code in one global, so it must use |
michael@0 | 13 | * loadSubScript directly. |
michael@0 | 14 | * |
michael@0 | 15 | * In the general case, this script should be used by importing Promise.jsm: |
michael@0 | 16 | * |
michael@0 | 17 | * Components.utils.import("resource://gre/modules/Promise.jsm"); |
michael@0 | 18 | * |
michael@0 | 19 | * More documentation can be found in the Promise.jsm module. |
michael@0 | 20 | */ |
michael@0 | 21 | |
michael@0 | 22 | //////////////////////////////////////////////////////////////////////////////// |
michael@0 | 23 | //// Globals |
michael@0 | 24 | |
michael@0 | 25 | Cu.import("resource://gre/modules/Services.jsm"); |
michael@0 | 26 | Cu.import("resource://gre/modules/XPCOMUtils.jsm"); |
michael@0 | 27 | |
michael@0 | 28 | const STATUS_PENDING = 0; |
michael@0 | 29 | const STATUS_RESOLVED = 1; |
michael@0 | 30 | const STATUS_REJECTED = 2; |
michael@0 | 31 | |
michael@0 | 32 | // These "private names" allow some properties of the Promise object to be |
michael@0 | 33 | // accessed only by this module, while still being visible on the object |
michael@0 | 34 | // manually when using a debugger. They don't strictly guarantee that the |
michael@0 | 35 | // properties are inaccessible by other code, but provide enough protection to |
michael@0 | 36 | // avoid using them by mistake. |
michael@0 | 37 | const salt = Math.floor(Math.random() * 100); |
michael@0 | 38 | const Name = (n) => "{private:" + n + ":" + salt + "}"; |
michael@0 | 39 | const N_STATUS = Name("status"); |
michael@0 | 40 | const N_VALUE = Name("value"); |
michael@0 | 41 | const N_HANDLERS = Name("handlers"); |
michael@0 | 42 | const N_WITNESS = Name("witness"); |
michael@0 | 43 | |
michael@0 | 44 | |
michael@0 | 45 | /////// Warn-upon-finalization mechanism |
michael@0 | 46 | // |
michael@0 | 47 | // One of the difficult problems with promises is locating uncaught |
michael@0 | 48 | // rejections. We adopt the following strategy: if a promise is rejected |
michael@0 | 49 | // at the time of its garbage-collection *and* if the promise is at the |
michael@0 | 50 | // end of a promise chain (i.e. |thatPromise.then| has never been |
michael@0 | 51 | // called), then we print a warning. |
michael@0 | 52 | // |
michael@0 | 53 | // let deferred = Promise.defer(); |
michael@0 | 54 | // let p = deferred.promise.then(); |
michael@0 | 55 | // deferred.reject(new Error("I am un uncaught error")); |
michael@0 | 56 | // deferred = null; |
michael@0 | 57 | // p = null; |
michael@0 | 58 | // |
michael@0 | 59 | // In this snippet, since |deferred.promise| is not the last in the |
michael@0 | 60 | // chain, no error will be reported for that promise. However, since |
michael@0 | 61 | // |p| is the last promise in the chain, the error will be reported |
michael@0 | 62 | // for |p|. |
michael@0 | 63 | // |
michael@0 | 64 | // Note that this may, in some cases, cause an error to be reported more |
michael@0 | 65 | // than once. For instance, consider: |
michael@0 | 66 | // |
michael@0 | 67 | // let deferred = Promise.defer(); |
michael@0 | 68 | // let p1 = deferred.promise.then(); |
michael@0 | 69 | // let p2 = deferred.promise.then(); |
michael@0 | 70 | // deferred.reject(new Error("I am an uncaught error")); |
michael@0 | 71 | // p1 = p2 = deferred = null; |
michael@0 | 72 | // |
michael@0 | 73 | // In this snippet, the error is reported both by p1 and by p2. |
michael@0 | 74 | // |
michael@0 | 75 | |
michael@0 | 76 | XPCOMUtils.defineLazyServiceGetter(this, "FinalizationWitnessService", |
michael@0 | 77 | "@mozilla.org/toolkit/finalizationwitness;1", |
michael@0 | 78 | "nsIFinalizationWitnessService"); |
michael@0 | 79 | |
michael@0 | 80 | let PendingErrors = { |
michael@0 | 81 | // An internal counter, used to generate unique id. |
michael@0 | 82 | _counter: 0, |
michael@0 | 83 | // Functions registered to be notified when a pending error |
michael@0 | 84 | // is reported as uncaught. |
michael@0 | 85 | _observers: new Set(), |
michael@0 | 86 | _map: new Map(), |
michael@0 | 87 | |
michael@0 | 88 | /** |
michael@0 | 89 | * Initialize PendingErrors |
michael@0 | 90 | */ |
michael@0 | 91 | init: function() { |
michael@0 | 92 | Services.obs.addObserver(function observe(aSubject, aTopic, aValue) { |
michael@0 | 93 | PendingErrors.report(aValue); |
michael@0 | 94 | }, "promise-finalization-witness", false); |
michael@0 | 95 | }, |
michael@0 | 96 | |
michael@0 | 97 | /** |
michael@0 | 98 | * Register an error as tracked. |
michael@0 | 99 | * |
michael@0 | 100 | * @return The unique identifier of the error. |
michael@0 | 101 | */ |
michael@0 | 102 | register: function(error) { |
michael@0 | 103 | let id = "pending-error-" + (this._counter++); |
michael@0 | 104 | // |
michael@0 | 105 | // At this stage, ideally, we would like to store the error itself |
michael@0 | 106 | // and delay any treatment until we are certain that we will need |
michael@0 | 107 | // to report that error. However, in the (unlikely but possible) |
michael@0 | 108 | // case the error holds a reference to the promise itself, doing so |
michael@0 | 109 | // would prevent the promise from being garbabe-collected, which |
michael@0 | 110 | // would both cause a memory leak and ensure that we cannot report |
michael@0 | 111 | // the uncaught error. |
michael@0 | 112 | // |
michael@0 | 113 | // To avoid this situation, we rather extract relevant data from |
michael@0 | 114 | // the error and further normalize it to strings. |
michael@0 | 115 | // |
michael@0 | 116 | let value = { |
michael@0 | 117 | date: new Date(), |
michael@0 | 118 | message: "" + error, |
michael@0 | 119 | fileName: null, |
michael@0 | 120 | stack: null, |
michael@0 | 121 | lineNumber: null |
michael@0 | 122 | }; |
michael@0 | 123 | try { // Defend against non-enumerable values |
michael@0 | 124 | if (error && error instanceof Ci.nsIException) { |
michael@0 | 125 | // nsIException does things a little differently. |
michael@0 | 126 | try { |
michael@0 | 127 | // For starters |.toString()| does not only contain the message, but |
michael@0 | 128 | // also the top stack frame, and we don't really want that. |
michael@0 | 129 | value.message = error.message; |
michael@0 | 130 | } catch (ex) { |
michael@0 | 131 | // Ignore field |
michael@0 | 132 | } |
michael@0 | 133 | try { |
michael@0 | 134 | // All lowercase filename. ;) |
michael@0 | 135 | value.fileName = error.filename; |
michael@0 | 136 | } catch (ex) { |
michael@0 | 137 | // Ignore field |
michael@0 | 138 | } |
michael@0 | 139 | try { |
michael@0 | 140 | value.lineNumber = error.lineNumber; |
michael@0 | 141 | } catch (ex) { |
michael@0 | 142 | // Ignore field |
michael@0 | 143 | } |
michael@0 | 144 | } else if (typeof error == "object" && error) { |
michael@0 | 145 | for (let k of ["fileName", "stack", "lineNumber"]) { |
michael@0 | 146 | try { // Defend against fallible getters and string conversions |
michael@0 | 147 | let v = error[k]; |
michael@0 | 148 | value[k] = v ? ("" + v) : null; |
michael@0 | 149 | } catch (ex) { |
michael@0 | 150 | // Ignore field |
michael@0 | 151 | } |
michael@0 | 152 | } |
michael@0 | 153 | } |
michael@0 | 154 | |
michael@0 | 155 | if (!value.stack) { |
michael@0 | 156 | // |error| is not an Error (or Error-alike). Try to figure out the stack. |
michael@0 | 157 | let stack = null; |
michael@0 | 158 | if (error && error.location && |
michael@0 | 159 | error.location instanceof Ci.nsIStackFrame) { |
michael@0 | 160 | // nsIException has full stack frames in the |.location| member. |
michael@0 | 161 | stack = error.location; |
michael@0 | 162 | } else { |
michael@0 | 163 | // Components.stack to the rescue! |
michael@0 | 164 | stack = Components.stack; |
michael@0 | 165 | // Remove those top frames that refer to Promise.jsm. |
michael@0 | 166 | while (stack) { |
michael@0 | 167 | if (!stack.filename.endsWith("/Promise.jsm")) { |
michael@0 | 168 | break; |
michael@0 | 169 | } |
michael@0 | 170 | stack = stack.caller; |
michael@0 | 171 | } |
michael@0 | 172 | } |
michael@0 | 173 | if (stack) { |
michael@0 | 174 | let frames = []; |
michael@0 | 175 | while (stack) { |
michael@0 | 176 | frames.push(stack); |
michael@0 | 177 | stack = stack.caller; |
michael@0 | 178 | } |
michael@0 | 179 | value.stack = frames.join("\n"); |
michael@0 | 180 | } |
michael@0 | 181 | } |
michael@0 | 182 | } catch (ex) { |
michael@0 | 183 | // Ignore value |
michael@0 | 184 | } |
michael@0 | 185 | this._map.set(id, value); |
michael@0 | 186 | return id; |
michael@0 | 187 | }, |
michael@0 | 188 | |
michael@0 | 189 | /** |
michael@0 | 190 | * Notify all observers that a pending error is now uncaught. |
michael@0 | 191 | * |
michael@0 | 192 | * @param id The identifier of the pending error, as returned by |
michael@0 | 193 | * |register|. |
michael@0 | 194 | */ |
michael@0 | 195 | report: function(id) { |
michael@0 | 196 | let value = this._map.get(id); |
michael@0 | 197 | if (!value) { |
michael@0 | 198 | return; // The error has already been reported |
michael@0 | 199 | } |
michael@0 | 200 | this._map.delete(id); |
michael@0 | 201 | for (let obs of this._observers.values()) { |
michael@0 | 202 | obs(value); |
michael@0 | 203 | } |
michael@0 | 204 | }, |
michael@0 | 205 | |
michael@0 | 206 | /** |
michael@0 | 207 | * Mark all pending errors are uncaught, notify the observers. |
michael@0 | 208 | */ |
michael@0 | 209 | flush: function() { |
michael@0 | 210 | // Since we are going to modify the map while walking it, |
michael@0 | 211 | // let's copying the keys first. |
michael@0 | 212 | let keys = [key for (key of this._map.keys())]; |
michael@0 | 213 | for (let key of keys) { |
michael@0 | 214 | this.report(key); |
michael@0 | 215 | } |
michael@0 | 216 | }, |
michael@0 | 217 | |
michael@0 | 218 | /** |
michael@0 | 219 | * Stop tracking an error, as this error has been caught, |
michael@0 | 220 | * eventually. |
michael@0 | 221 | */ |
michael@0 | 222 | unregister: function(id) { |
michael@0 | 223 | this._map.delete(id); |
michael@0 | 224 | }, |
michael@0 | 225 | |
michael@0 | 226 | /** |
michael@0 | 227 | * Add an observer notified when an error is reported as uncaught. |
michael@0 | 228 | * |
michael@0 | 229 | * @param {function} observer A function notified when an error is |
michael@0 | 230 | * reported as uncaught. Its arguments are |
michael@0 | 231 | * {message, date, fileName, stack, lineNumber} |
michael@0 | 232 | * All arguments are optional. |
michael@0 | 233 | */ |
michael@0 | 234 | addObserver: function(observer) { |
michael@0 | 235 | this._observers.add(observer); |
michael@0 | 236 | }, |
michael@0 | 237 | |
michael@0 | 238 | /** |
michael@0 | 239 | * Remove an observer added with addObserver |
michael@0 | 240 | */ |
michael@0 | 241 | removeObserver: function(observer) { |
michael@0 | 242 | this._observers.delete(observer); |
michael@0 | 243 | }, |
michael@0 | 244 | |
michael@0 | 245 | /** |
michael@0 | 246 | * Remove all the observers added with addObserver |
michael@0 | 247 | */ |
michael@0 | 248 | removeAllObservers: function() { |
michael@0 | 249 | this._observers.clear(); |
michael@0 | 250 | } |
michael@0 | 251 | }; |
michael@0 | 252 | PendingErrors.init(); |
michael@0 | 253 | |
michael@0 | 254 | // Default mechanism for displaying errors |
michael@0 | 255 | PendingErrors.addObserver(function(details) { |
michael@0 | 256 | let error = Cc['@mozilla.org/scripterror;1'].createInstance(Ci.nsIScriptError); |
michael@0 | 257 | if (!error || !Services.console) { |
michael@0 | 258 | // Too late during shutdown to use the nsIConsole |
michael@0 | 259 | dump("*************************\n"); |
michael@0 | 260 | dump("A promise chain failed to handle a rejection\n\n"); |
michael@0 | 261 | dump("On: " + details.date + "\n"); |
michael@0 | 262 | dump("Full message: " + details.message + "\n"); |
michael@0 | 263 | dump("See https://developer.mozilla.org/Mozilla/JavaScript_code_modules/Promise.jsm/Promise\n"); |
michael@0 | 264 | dump("Full stack: " + (details.stack||"not available") + "\n"); |
michael@0 | 265 | dump("*************************\n"); |
michael@0 | 266 | return; |
michael@0 | 267 | } |
michael@0 | 268 | let message = details.message; |
michael@0 | 269 | if (details.stack) { |
michael@0 | 270 | message += "\nFull Stack: " + details.stack; |
michael@0 | 271 | } |
michael@0 | 272 | error.init( |
michael@0 | 273 | /*message*/"A promise chain failed to handle a rejection.\n\n" + |
michael@0 | 274 | "Date: " + details.date + "\nFull Message: " + details.message, |
michael@0 | 275 | /*sourceName*/ details.fileName, |
michael@0 | 276 | /*sourceLine*/ details.lineNumber?("" + details.lineNumber):0, |
michael@0 | 277 | /*lineNumber*/ details.lineNumber || 0, |
michael@0 | 278 | /*columnNumber*/ 0, |
michael@0 | 279 | /*flags*/ Ci.nsIScriptError.errorFlag, |
michael@0 | 280 | /*category*/ "chrome javascript"); |
michael@0 | 281 | Services.console.logMessage(error); |
michael@0 | 282 | }); |
michael@0 | 283 | |
michael@0 | 284 | |
michael@0 | 285 | ///////// Additional warnings for developers |
michael@0 | 286 | // |
michael@0 | 287 | // The following error types are considered programmer errors, which should be |
michael@0 | 288 | // reported (possibly redundantly) so as to let programmers fix their code. |
michael@0 | 289 | const ERRORS_TO_REPORT = ["EvalError", "RangeError", "ReferenceError", "TypeError"]; |
michael@0 | 290 | |
michael@0 | 291 | //////////////////////////////////////////////////////////////////////////////// |
michael@0 | 292 | //// Promise |
michael@0 | 293 | |
michael@0 | 294 | /** |
michael@0 | 295 | * The Promise constructor. Creates a new promise given an executor callback. |
michael@0 | 296 | * The executor callback is called with the resolve and reject handlers. |
michael@0 | 297 | * |
michael@0 | 298 | * @param aExecutor |
michael@0 | 299 | * The callback that will be called with resolve and reject. |
michael@0 | 300 | */ |
michael@0 | 301 | this.Promise = function Promise(aExecutor) |
michael@0 | 302 | { |
michael@0 | 303 | if (typeof(aExecutor) != "function") { |
michael@0 | 304 | throw new TypeError("Promise constructor must be called with an executor."); |
michael@0 | 305 | } |
michael@0 | 306 | |
michael@0 | 307 | /* |
michael@0 | 308 | * Internal status of the promise. This can be equal to STATUS_PENDING, |
michael@0 | 309 | * STATUS_RESOLVED, or STATUS_REJECTED. |
michael@0 | 310 | */ |
michael@0 | 311 | Object.defineProperty(this, N_STATUS, { value: STATUS_PENDING, |
michael@0 | 312 | writable: true }); |
michael@0 | 313 | |
michael@0 | 314 | /* |
michael@0 | 315 | * When the N_STATUS property is STATUS_RESOLVED, this contains the final |
michael@0 | 316 | * resolution value, that cannot be a promise, because resolving with a |
michael@0 | 317 | * promise will cause its state to be eventually propagated instead. When the |
michael@0 | 318 | * N_STATUS property is STATUS_REJECTED, this contains the final rejection |
michael@0 | 319 | * reason, that could be a promise, even if this is uncommon. |
michael@0 | 320 | */ |
michael@0 | 321 | Object.defineProperty(this, N_VALUE, { writable: true }); |
michael@0 | 322 | |
michael@0 | 323 | /* |
michael@0 | 324 | * Array of Handler objects registered by the "then" method, and not processed |
michael@0 | 325 | * yet. Handlers are removed when the promise is resolved or rejected. |
michael@0 | 326 | */ |
michael@0 | 327 | Object.defineProperty(this, N_HANDLERS, { value: [] }); |
michael@0 | 328 | |
michael@0 | 329 | /** |
michael@0 | 330 | * When the N_STATUS property is STATUS_REJECTED and until there is |
michael@0 | 331 | * a rejection callback, this contains an array |
michael@0 | 332 | * - {string} id An id for use with |PendingErrors|; |
michael@0 | 333 | * - {FinalizationWitness} witness A witness broadcasting |id| on |
michael@0 | 334 | * notification "promise-finalization-witness". |
michael@0 | 335 | */ |
michael@0 | 336 | Object.defineProperty(this, N_WITNESS, { writable: true }); |
michael@0 | 337 | |
michael@0 | 338 | Object.seal(this); |
michael@0 | 339 | |
michael@0 | 340 | let resolve = PromiseWalker.completePromise |
michael@0 | 341 | .bind(PromiseWalker, this, STATUS_RESOLVED); |
michael@0 | 342 | let reject = PromiseWalker.completePromise |
michael@0 | 343 | .bind(PromiseWalker, this, STATUS_REJECTED); |
michael@0 | 344 | |
michael@0 | 345 | try { |
michael@0 | 346 | aExecutor.call(undefined, resolve, reject); |
michael@0 | 347 | } catch (ex) { |
michael@0 | 348 | reject(ex); |
michael@0 | 349 | } |
michael@0 | 350 | } |
michael@0 | 351 | |
michael@0 | 352 | /** |
michael@0 | 353 | * Calls one of the provided functions as soon as this promise is either |
michael@0 | 354 | * resolved or rejected. A new promise is returned, whose state evolves |
michael@0 | 355 | * depending on this promise and the provided callback functions. |
michael@0 | 356 | * |
michael@0 | 357 | * The appropriate callback is always invoked after this method returns, even |
michael@0 | 358 | * if this promise is already resolved or rejected. You can also call the |
michael@0 | 359 | * "then" method multiple times on the same promise, and the callbacks will be |
michael@0 | 360 | * invoked in the same order as they were registered. |
michael@0 | 361 | * |
michael@0 | 362 | * @param aOnResolve |
michael@0 | 363 | * If the promise is resolved, this function is invoked with the |
michael@0 | 364 | * resolution value of the promise as its only argument, and the |
michael@0 | 365 | * outcome of the function determines the state of the new promise |
michael@0 | 366 | * returned by the "then" method. In case this parameter is not a |
michael@0 | 367 | * function (usually "null"), the new promise returned by the "then" |
michael@0 | 368 | * method is resolved with the same value as the original promise. |
michael@0 | 369 | * |
michael@0 | 370 | * @param aOnReject |
michael@0 | 371 | * If the promise is rejected, this function is invoked with the |
michael@0 | 372 | * rejection reason of the promise as its only argument, and the |
michael@0 | 373 | * outcome of the function determines the state of the new promise |
michael@0 | 374 | * returned by the "then" method. In case this parameter is not a |
michael@0 | 375 | * function (usually left "undefined"), the new promise returned by the |
michael@0 | 376 | * "then" method is rejected with the same reason as the original |
michael@0 | 377 | * promise. |
michael@0 | 378 | * |
michael@0 | 379 | * @return A new promise that is initially pending, then assumes a state that |
michael@0 | 380 | * depends on the outcome of the invoked callback function: |
michael@0 | 381 | * - If the callback returns a value that is not a promise, including |
michael@0 | 382 | * "undefined", the new promise is resolved with this resolution |
michael@0 | 383 | * value, even if the original promise was rejected. |
michael@0 | 384 | * - If the callback throws an exception, the new promise is rejected |
michael@0 | 385 | * with the exception as the rejection reason, even if the original |
michael@0 | 386 | * promise was resolved. |
michael@0 | 387 | * - If the callback returns a promise, the new promise will |
michael@0 | 388 | * eventually assume the same state as the returned promise. |
michael@0 | 389 | * |
michael@0 | 390 | * @note If the aOnResolve callback throws an exception, the aOnReject |
michael@0 | 391 | * callback is not invoked. You can register a rejection callback on |
michael@0 | 392 | * the returned promise instead, to process any exception occurred in |
michael@0 | 393 | * either of the callbacks registered on this promise. |
michael@0 | 394 | */ |
michael@0 | 395 | Promise.prototype.then = function (aOnResolve, aOnReject) |
michael@0 | 396 | { |
michael@0 | 397 | let handler = new Handler(this, aOnResolve, aOnReject); |
michael@0 | 398 | this[N_HANDLERS].push(handler); |
michael@0 | 399 | |
michael@0 | 400 | // Ensure the handler is scheduled for processing if this promise is already |
michael@0 | 401 | // resolved or rejected. |
michael@0 | 402 | if (this[N_STATUS] != STATUS_PENDING) { |
michael@0 | 403 | |
michael@0 | 404 | // This promise is not the last in the chain anymore. Remove any watchdog. |
michael@0 | 405 | if (this[N_WITNESS] != null) { |
michael@0 | 406 | let [id, witness] = this[N_WITNESS]; |
michael@0 | 407 | this[N_WITNESS] = null; |
michael@0 | 408 | witness.forget(); |
michael@0 | 409 | PendingErrors.unregister(id); |
michael@0 | 410 | } |
michael@0 | 411 | |
michael@0 | 412 | PromiseWalker.schedulePromise(this); |
michael@0 | 413 | } |
michael@0 | 414 | |
michael@0 | 415 | return handler.nextPromise; |
michael@0 | 416 | }; |
michael@0 | 417 | |
michael@0 | 418 | /** |
michael@0 | 419 | * Invokes `promise.then` with undefined for the resolve handler and a given |
michael@0 | 420 | * reject handler. |
michael@0 | 421 | * |
michael@0 | 422 | * @param aOnReject |
michael@0 | 423 | * The rejection handler. |
michael@0 | 424 | * |
michael@0 | 425 | * @return A new pending promise returned. |
michael@0 | 426 | * |
michael@0 | 427 | * @see Promise.prototype.then |
michael@0 | 428 | */ |
michael@0 | 429 | Promise.prototype.catch = function (aOnReject) |
michael@0 | 430 | { |
michael@0 | 431 | return this.then(undefined, aOnReject); |
michael@0 | 432 | }; |
michael@0 | 433 | |
michael@0 | 434 | /** |
michael@0 | 435 | * Creates a new pending promise and provides methods to resolve or reject it. |
michael@0 | 436 | * |
michael@0 | 437 | * @return A new object, containing the new promise in the "promise" property, |
michael@0 | 438 | * and the methods to change its state in the "resolve" and "reject" |
michael@0 | 439 | * properties. See the Deferred documentation for details. |
michael@0 | 440 | */ |
michael@0 | 441 | Promise.defer = function () |
michael@0 | 442 | { |
michael@0 | 443 | return new Deferred(); |
michael@0 | 444 | }; |
michael@0 | 445 | |
michael@0 | 446 | /** |
michael@0 | 447 | * Creates a new promise resolved with the specified value, or propagates the |
michael@0 | 448 | * state of an existing promise. |
michael@0 | 449 | * |
michael@0 | 450 | * @param aValue |
michael@0 | 451 | * If this value is not a promise, including "undefined", it becomes |
michael@0 | 452 | * the resolution value of the returned promise. If this value is a |
michael@0 | 453 | * promise, then the returned promise will eventually assume the same |
michael@0 | 454 | * state as the provided promise. |
michael@0 | 455 | * |
michael@0 | 456 | * @return A promise that can be pending, resolved, or rejected. |
michael@0 | 457 | */ |
michael@0 | 458 | Promise.resolve = function (aValue) |
michael@0 | 459 | { |
michael@0 | 460 | if (aValue && typeof(aValue) == "function" && aValue.isAsyncFunction) { |
michael@0 | 461 | throw new TypeError( |
michael@0 | 462 | "Cannot resolve a promise with an async function. " + |
michael@0 | 463 | "You should either invoke the async function first " + |
michael@0 | 464 | "or use 'Task.spawn' instead of 'Task.async' to start " + |
michael@0 | 465 | "the Task and return its promise."); |
michael@0 | 466 | } |
michael@0 | 467 | |
michael@0 | 468 | if (aValue instanceof Promise) { |
michael@0 | 469 | return aValue; |
michael@0 | 470 | } |
michael@0 | 471 | |
michael@0 | 472 | return new Promise((aResolve) => aResolve(aValue)); |
michael@0 | 473 | }; |
michael@0 | 474 | |
michael@0 | 475 | /** |
michael@0 | 476 | * Creates a new promise rejected with the specified reason. |
michael@0 | 477 | * |
michael@0 | 478 | * @param aReason |
michael@0 | 479 | * The rejection reason for the returned promise. Although the reason |
michael@0 | 480 | * can be "undefined", it is generally an Error object, like in |
michael@0 | 481 | * exception handling. |
michael@0 | 482 | * |
michael@0 | 483 | * @return A rejected promise. |
michael@0 | 484 | * |
michael@0 | 485 | * @note The aReason argument should not be a promise. Using a rejected |
michael@0 | 486 | * promise for the value of aReason would make the rejection reason |
michael@0 | 487 | * equal to the rejected promise itself, and not its rejection reason. |
michael@0 | 488 | */ |
michael@0 | 489 | Promise.reject = function (aReason) |
michael@0 | 490 | { |
michael@0 | 491 | return new Promise((_, aReject) => aReject(aReason)); |
michael@0 | 492 | }; |
michael@0 | 493 | |
michael@0 | 494 | /** |
michael@0 | 495 | * Returns a promise that is resolved or rejected when all values are |
michael@0 | 496 | * resolved or any is rejected. |
michael@0 | 497 | * |
michael@0 | 498 | * @param aValues |
michael@0 | 499 | * Iterable of promises that may be pending, resolved, or rejected. When |
michael@0 | 500 | * all are resolved or any is rejected, the returned promise will be |
michael@0 | 501 | * resolved or rejected as well. |
michael@0 | 502 | * |
michael@0 | 503 | * @return A new promise that is fulfilled when all values are resolved or |
michael@0 | 504 | * that is rejected when any of the values are rejected. Its |
michael@0 | 505 | * resolution value will be an array of all resolved values in the |
michael@0 | 506 | * given order, or undefined if aValues is an empty array. The reject |
michael@0 | 507 | * reason will be forwarded from the first promise in the list of |
michael@0 | 508 | * given promises to be rejected. |
michael@0 | 509 | */ |
michael@0 | 510 | Promise.all = function (aValues) |
michael@0 | 511 | { |
michael@0 | 512 | if (aValues == null || typeof(aValues["@@iterator"]) != "function") { |
michael@0 | 513 | throw new Error("Promise.all() expects an iterable."); |
michael@0 | 514 | } |
michael@0 | 515 | |
michael@0 | 516 | return new Promise((resolve, reject) => { |
michael@0 | 517 | let values = Array.isArray(aValues) ? aValues : [...aValues]; |
michael@0 | 518 | let countdown = values.length; |
michael@0 | 519 | let resolutionValues = new Array(countdown); |
michael@0 | 520 | |
michael@0 | 521 | if (!countdown) { |
michael@0 | 522 | resolve(resolutionValues); |
michael@0 | 523 | return; |
michael@0 | 524 | } |
michael@0 | 525 | |
michael@0 | 526 | function checkForCompletion(aValue, aIndex) { |
michael@0 | 527 | resolutionValues[aIndex] = aValue; |
michael@0 | 528 | if (--countdown === 0) { |
michael@0 | 529 | resolve(resolutionValues); |
michael@0 | 530 | } |
michael@0 | 531 | } |
michael@0 | 532 | |
michael@0 | 533 | for (let i = 0; i < values.length; i++) { |
michael@0 | 534 | let index = i; |
michael@0 | 535 | let value = values[i]; |
michael@0 | 536 | let resolver = val => checkForCompletion(val, index); |
michael@0 | 537 | |
michael@0 | 538 | if (value && typeof(value.then) == "function") { |
michael@0 | 539 | value.then(resolver, reject); |
michael@0 | 540 | } else { |
michael@0 | 541 | // Given value is not a promise, forward it as a resolution value. |
michael@0 | 542 | resolver(value); |
michael@0 | 543 | } |
michael@0 | 544 | } |
michael@0 | 545 | }); |
michael@0 | 546 | }; |
michael@0 | 547 | |
michael@0 | 548 | /** |
michael@0 | 549 | * Returns a promise that is resolved or rejected when the first value is |
michael@0 | 550 | * resolved or rejected, taking on the value or reason of that promise. |
michael@0 | 551 | * |
michael@0 | 552 | * @param aValues |
michael@0 | 553 | * Iterable of values or promises that may be pending, resolved, or |
michael@0 | 554 | * rejected. When any is resolved or rejected, the returned promise will |
michael@0 | 555 | * be resolved or rejected as to the given value or reason. |
michael@0 | 556 | * |
michael@0 | 557 | * @return A new promise that is fulfilled when any values are resolved or |
michael@0 | 558 | * rejected. Its resolution value will be forwarded from the resolution |
michael@0 | 559 | * value or rejection reason. |
michael@0 | 560 | */ |
michael@0 | 561 | Promise.race = function (aValues) |
michael@0 | 562 | { |
michael@0 | 563 | if (aValues == null || typeof(aValues["@@iterator"]) != "function") { |
michael@0 | 564 | throw new Error("Promise.race() expects an iterable."); |
michael@0 | 565 | } |
michael@0 | 566 | |
michael@0 | 567 | return new Promise((resolve, reject) => { |
michael@0 | 568 | for (let value of aValues) { |
michael@0 | 569 | Promise.resolve(value).then(resolve, reject); |
michael@0 | 570 | } |
michael@0 | 571 | }); |
michael@0 | 572 | }; |
michael@0 | 573 | |
michael@0 | 574 | Promise.Debugging = { |
michael@0 | 575 | /** |
michael@0 | 576 | * Add an observer notified when an error is reported as uncaught. |
michael@0 | 577 | * |
michael@0 | 578 | * @param {function} observer A function notified when an error is |
michael@0 | 579 | * reported as uncaught. Its arguments are |
michael@0 | 580 | * {message, date, fileName, stack, lineNumber} |
michael@0 | 581 | * All arguments are optional. |
michael@0 | 582 | */ |
michael@0 | 583 | addUncaughtErrorObserver: function(observer) { |
michael@0 | 584 | PendingErrors.addObserver(observer); |
michael@0 | 585 | }, |
michael@0 | 586 | |
michael@0 | 587 | /** |
michael@0 | 588 | * Remove an observer added with addUncaughtErrorObserver |
michael@0 | 589 | * |
michael@0 | 590 | * @param {function} An observer registered with |
michael@0 | 591 | * addUncaughtErrorObserver. |
michael@0 | 592 | */ |
michael@0 | 593 | removeUncaughtErrorObserver: function(observer) { |
michael@0 | 594 | PendingErrors.removeObserver(observer); |
michael@0 | 595 | }, |
michael@0 | 596 | |
michael@0 | 597 | /** |
michael@0 | 598 | * Remove all the observers added with addUncaughtErrorObserver |
michael@0 | 599 | */ |
michael@0 | 600 | clearUncaughtErrorObservers: function() { |
michael@0 | 601 | PendingErrors.removeAllObservers(); |
michael@0 | 602 | }, |
michael@0 | 603 | |
michael@0 | 604 | /** |
michael@0 | 605 | * Force all pending errors to be reported immediately as uncaught. |
michael@0 | 606 | * Note that this may cause some false positives. |
michael@0 | 607 | */ |
michael@0 | 608 | flushUncaughtErrors: function() { |
michael@0 | 609 | PendingErrors.flush(); |
michael@0 | 610 | }, |
michael@0 | 611 | }; |
michael@0 | 612 | Object.freeze(Promise.Debugging); |
michael@0 | 613 | |
michael@0 | 614 | Object.freeze(Promise); |
michael@0 | 615 | |
michael@0 | 616 | //////////////////////////////////////////////////////////////////////////////// |
michael@0 | 617 | //// PromiseWalker |
michael@0 | 618 | |
michael@0 | 619 | /** |
michael@0 | 620 | * This singleton object invokes the handlers registered on resolved and |
michael@0 | 621 | * rejected promises, ensuring that processing is not recursive and is done in |
michael@0 | 622 | * the same order as registration occurred on each promise. |
michael@0 | 623 | * |
michael@0 | 624 | * There is no guarantee on the order of execution of handlers registered on |
michael@0 | 625 | * different promises. |
michael@0 | 626 | */ |
michael@0 | 627 | this.PromiseWalker = { |
michael@0 | 628 | /** |
michael@0 | 629 | * Singleton array of all the unprocessed handlers currently registered on |
michael@0 | 630 | * resolved or rejected promises. Handlers are removed from the array as soon |
michael@0 | 631 | * as they are processed. |
michael@0 | 632 | */ |
michael@0 | 633 | handlers: [], |
michael@0 | 634 | |
michael@0 | 635 | /** |
michael@0 | 636 | * Called when a promise needs to change state to be resolved or rejected. |
michael@0 | 637 | * |
michael@0 | 638 | * @param aPromise |
michael@0 | 639 | * Promise that needs to change state. If this is already resolved or |
michael@0 | 640 | * rejected, this method has no effect. |
michael@0 | 641 | * @param aStatus |
michael@0 | 642 | * New desired status, either STATUS_RESOLVED or STATUS_REJECTED. |
michael@0 | 643 | * @param aValue |
michael@0 | 644 | * Associated resolution value or rejection reason. |
michael@0 | 645 | */ |
michael@0 | 646 | completePromise: function (aPromise, aStatus, aValue) |
michael@0 | 647 | { |
michael@0 | 648 | // Do nothing if the promise is already resolved or rejected. |
michael@0 | 649 | if (aPromise[N_STATUS] != STATUS_PENDING) { |
michael@0 | 650 | return; |
michael@0 | 651 | } |
michael@0 | 652 | |
michael@0 | 653 | // Resolving with another promise will cause this promise to eventually |
michael@0 | 654 | // assume the state of the provided promise. |
michael@0 | 655 | if (aStatus == STATUS_RESOLVED && aValue && |
michael@0 | 656 | typeof(aValue.then) == "function") { |
michael@0 | 657 | aValue.then(this.completePromise.bind(this, aPromise, STATUS_RESOLVED), |
michael@0 | 658 | this.completePromise.bind(this, aPromise, STATUS_REJECTED)); |
michael@0 | 659 | return; |
michael@0 | 660 | } |
michael@0 | 661 | |
michael@0 | 662 | // Change the promise status and schedule our handlers for processing. |
michael@0 | 663 | aPromise[N_STATUS] = aStatus; |
michael@0 | 664 | aPromise[N_VALUE] = aValue; |
michael@0 | 665 | if (aPromise[N_HANDLERS].length > 0) { |
michael@0 | 666 | this.schedulePromise(aPromise); |
michael@0 | 667 | } else if (aStatus == STATUS_REJECTED) { |
michael@0 | 668 | // This is a rejection and the promise is the last in the chain. |
michael@0 | 669 | // For the time being we therefore have an uncaught error. |
michael@0 | 670 | let id = PendingErrors.register(aValue); |
michael@0 | 671 | let witness = |
michael@0 | 672 | FinalizationWitnessService.make("promise-finalization-witness", id); |
michael@0 | 673 | aPromise[N_WITNESS] = [id, witness]; |
michael@0 | 674 | } |
michael@0 | 675 | }, |
michael@0 | 676 | |
michael@0 | 677 | /** |
michael@0 | 678 | * Sets up the PromiseWalker loop to start on the next tick of the event loop |
michael@0 | 679 | */ |
michael@0 | 680 | scheduleWalkerLoop: function() |
michael@0 | 681 | { |
michael@0 | 682 | this.walkerLoopScheduled = true; |
michael@0 | 683 | Services.tm.currentThread.dispatch(this.walkerLoop, |
michael@0 | 684 | Ci.nsIThread.DISPATCH_NORMAL); |
michael@0 | 685 | }, |
michael@0 | 686 | |
michael@0 | 687 | /** |
michael@0 | 688 | * Schedules the resolution or rejection handlers registered on the provided |
michael@0 | 689 | * promise for processing. |
michael@0 | 690 | * |
michael@0 | 691 | * @param aPromise |
michael@0 | 692 | * Resolved or rejected promise whose handlers should be processed. It |
michael@0 | 693 | * is expected that this promise has at least one handler to process. |
michael@0 | 694 | */ |
michael@0 | 695 | schedulePromise: function (aPromise) |
michael@0 | 696 | { |
michael@0 | 697 | // Migrate the handlers from the provided promise to the global list. |
michael@0 | 698 | for (let handler of aPromise[N_HANDLERS]) { |
michael@0 | 699 | this.handlers.push(handler); |
michael@0 | 700 | } |
michael@0 | 701 | aPromise[N_HANDLERS].length = 0; |
michael@0 | 702 | |
michael@0 | 703 | // Schedule the walker loop on the next tick of the event loop. |
michael@0 | 704 | if (!this.walkerLoopScheduled) { |
michael@0 | 705 | this.scheduleWalkerLoop(); |
michael@0 | 706 | } |
michael@0 | 707 | }, |
michael@0 | 708 | |
michael@0 | 709 | /** |
michael@0 | 710 | * Indicates whether the walker loop is currently scheduled for execution on |
michael@0 | 711 | * the next tick of the event loop. |
michael@0 | 712 | */ |
michael@0 | 713 | walkerLoopScheduled: false, |
michael@0 | 714 | |
michael@0 | 715 | /** |
michael@0 | 716 | * Processes all the known handlers during this tick of the event loop. This |
michael@0 | 717 | * eager processing is done to avoid unnecessarily exiting and re-entering the |
michael@0 | 718 | * JavaScript context for each handler on a resolved or rejected promise. |
michael@0 | 719 | * |
michael@0 | 720 | * This function is called with "this" bound to the PromiseWalker object. |
michael@0 | 721 | */ |
michael@0 | 722 | walkerLoop: function () |
michael@0 | 723 | { |
michael@0 | 724 | // If there is more than one handler waiting, reschedule the walker loop |
michael@0 | 725 | // immediately. Otherwise, use walkerLoopScheduled to tell schedulePromise() |
michael@0 | 726 | // to reschedule the loop if it adds more handlers to the queue. This makes |
michael@0 | 727 | // this walker resilient to the case where one handler does not return, but |
michael@0 | 728 | // starts a nested event loop. In that case, the newly scheduled walker will |
michael@0 | 729 | // take over. In the common case, the newly scheduled walker will be invoked |
michael@0 | 730 | // after this one has returned, with no actual handler to process. This |
michael@0 | 731 | // small overhead is required to make nested event loops work correctly, but |
michael@0 | 732 | // occurs at most once per resolution chain, thus having only a minor |
michael@0 | 733 | // impact on overall performance. |
michael@0 | 734 | if (this.handlers.length > 1) { |
michael@0 | 735 | this.scheduleWalkerLoop(); |
michael@0 | 736 | } else { |
michael@0 | 737 | this.walkerLoopScheduled = false; |
michael@0 | 738 | } |
michael@0 | 739 | |
michael@0 | 740 | // Process all the known handlers eagerly. |
michael@0 | 741 | while (this.handlers.length > 0) { |
michael@0 | 742 | this.handlers.shift().process(); |
michael@0 | 743 | } |
michael@0 | 744 | }, |
michael@0 | 745 | }; |
michael@0 | 746 | |
michael@0 | 747 | // Bind the function to the singleton once. |
michael@0 | 748 | PromiseWalker.walkerLoop = PromiseWalker.walkerLoop.bind(PromiseWalker); |
michael@0 | 749 | |
michael@0 | 750 | //////////////////////////////////////////////////////////////////////////////// |
michael@0 | 751 | //// Deferred |
michael@0 | 752 | |
michael@0 | 753 | /** |
michael@0 | 754 | * Returned by "Promise.defer" to provide a new promise along with methods to |
michael@0 | 755 | * change its state. |
michael@0 | 756 | */ |
michael@0 | 757 | function Deferred() |
michael@0 | 758 | { |
michael@0 | 759 | this.promise = new Promise((aResolve, aReject) => { |
michael@0 | 760 | this.resolve = aResolve; |
michael@0 | 761 | this.reject = aReject; |
michael@0 | 762 | }); |
michael@0 | 763 | Object.freeze(this); |
michael@0 | 764 | } |
michael@0 | 765 | |
michael@0 | 766 | Deferred.prototype = { |
michael@0 | 767 | /** |
michael@0 | 768 | * A newly created promise, initially in the pending state. |
michael@0 | 769 | */ |
michael@0 | 770 | promise: null, |
michael@0 | 771 | |
michael@0 | 772 | /** |
michael@0 | 773 | * Resolves the associated promise with the specified value, or propagates the |
michael@0 | 774 | * state of an existing promise. If the associated promise has already been |
michael@0 | 775 | * resolved or rejected, this method does nothing. |
michael@0 | 776 | * |
michael@0 | 777 | * This function is bound to its associated promise when "Promise.defer" is |
michael@0 | 778 | * called, and can be called with any value of "this". |
michael@0 | 779 | * |
michael@0 | 780 | * @param aValue |
michael@0 | 781 | * If this value is not a promise, including "undefined", it becomes |
michael@0 | 782 | * the resolution value of the associated promise. If this value is a |
michael@0 | 783 | * promise, then the associated promise will eventually assume the same |
michael@0 | 784 | * state as the provided promise. |
michael@0 | 785 | * |
michael@0 | 786 | * @note Calling this method with a pending promise as the aValue argument, |
michael@0 | 787 | * and then calling it again with another value before the promise is |
michael@0 | 788 | * resolved or rejected, has unspecified behavior and should be avoided. |
michael@0 | 789 | */ |
michael@0 | 790 | resolve: null, |
michael@0 | 791 | |
michael@0 | 792 | /** |
michael@0 | 793 | * Rejects the associated promise with the specified reason. If the promise |
michael@0 | 794 | * has already been resolved or rejected, this method does nothing. |
michael@0 | 795 | * |
michael@0 | 796 | * This function is bound to its associated promise when "Promise.defer" is |
michael@0 | 797 | * called, and can be called with any value of "this". |
michael@0 | 798 | * |
michael@0 | 799 | * @param aReason |
michael@0 | 800 | * The rejection reason for the associated promise. Although the |
michael@0 | 801 | * reason can be "undefined", it is generally an Error object, like in |
michael@0 | 802 | * exception handling. |
michael@0 | 803 | * |
michael@0 | 804 | * @note The aReason argument should not generally be a promise. In fact, |
michael@0 | 805 | * using a rejected promise for the value of aReason would make the |
michael@0 | 806 | * rejection reason equal to the rejected promise itself, not to the |
michael@0 | 807 | * rejection reason of the rejected promise. |
michael@0 | 808 | */ |
michael@0 | 809 | reject: null, |
michael@0 | 810 | }; |
michael@0 | 811 | |
michael@0 | 812 | //////////////////////////////////////////////////////////////////////////////// |
michael@0 | 813 | //// Handler |
michael@0 | 814 | |
michael@0 | 815 | /** |
michael@0 | 816 | * Handler registered on a promise by the "then" function. |
michael@0 | 817 | */ |
michael@0 | 818 | function Handler(aThisPromise, aOnResolve, aOnReject) |
michael@0 | 819 | { |
michael@0 | 820 | this.thisPromise = aThisPromise; |
michael@0 | 821 | this.onResolve = aOnResolve; |
michael@0 | 822 | this.onReject = aOnReject; |
michael@0 | 823 | this.nextPromise = new Promise(() => {}); |
michael@0 | 824 | } |
michael@0 | 825 | |
michael@0 | 826 | Handler.prototype = { |
michael@0 | 827 | /** |
michael@0 | 828 | * Promise on which the "then" method was called. |
michael@0 | 829 | */ |
michael@0 | 830 | thisPromise: null, |
michael@0 | 831 | |
michael@0 | 832 | /** |
michael@0 | 833 | * Unmodified resolution handler provided to the "then" method. |
michael@0 | 834 | */ |
michael@0 | 835 | onResolve: null, |
michael@0 | 836 | |
michael@0 | 837 | /** |
michael@0 | 838 | * Unmodified rejection handler provided to the "then" method. |
michael@0 | 839 | */ |
michael@0 | 840 | onReject: null, |
michael@0 | 841 | |
michael@0 | 842 | /** |
michael@0 | 843 | * New promise that will be returned by the "then" method. |
michael@0 | 844 | */ |
michael@0 | 845 | nextPromise: null, |
michael@0 | 846 | |
michael@0 | 847 | /** |
michael@0 | 848 | * Called after thisPromise is resolved or rejected, invokes the appropriate |
michael@0 | 849 | * callback and propagates the result to nextPromise. |
michael@0 | 850 | */ |
michael@0 | 851 | process: function() |
michael@0 | 852 | { |
michael@0 | 853 | // The state of this promise is propagated unless a handler is defined. |
michael@0 | 854 | let nextStatus = this.thisPromise[N_STATUS]; |
michael@0 | 855 | let nextValue = this.thisPromise[N_VALUE]; |
michael@0 | 856 | |
michael@0 | 857 | try { |
michael@0 | 858 | // If a handler is defined for either resolution or rejection, invoke it |
michael@0 | 859 | // to determine the state of the next promise, that will be resolved with |
michael@0 | 860 | // the returned value, that can also be another promise. |
michael@0 | 861 | if (nextStatus == STATUS_RESOLVED) { |
michael@0 | 862 | if (typeof(this.onResolve) == "function") { |
michael@0 | 863 | nextValue = this.onResolve.call(undefined, nextValue); |
michael@0 | 864 | } |
michael@0 | 865 | } else if (typeof(this.onReject) == "function") { |
michael@0 | 866 | nextValue = this.onReject.call(undefined, nextValue); |
michael@0 | 867 | nextStatus = STATUS_RESOLVED; |
michael@0 | 868 | } |
michael@0 | 869 | } catch (ex) { |
michael@0 | 870 | |
michael@0 | 871 | // An exception has occurred in the handler. |
michael@0 | 872 | |
michael@0 | 873 | if (ex && typeof ex == "object" && "name" in ex && |
michael@0 | 874 | ERRORS_TO_REPORT.indexOf(ex.name) != -1) { |
michael@0 | 875 | |
michael@0 | 876 | // We suspect that the exception is a programmer error, so we now |
michael@0 | 877 | // display it using dump(). Note that we do not use Cu.reportError as |
michael@0 | 878 | // we assume that this is a programming error, so we do not want end |
michael@0 | 879 | // users to see it. Also, if the programmer handles errors correctly, |
michael@0 | 880 | // they will either treat the error or log them somewhere. |
michael@0 | 881 | |
michael@0 | 882 | dump("*************************\n"); |
michael@0 | 883 | dump("A coding exception was thrown in a Promise " + |
michael@0 | 884 | ((nextStatus == STATUS_RESOLVED) ? "resolution":"rejection") + |
michael@0 | 885 | " callback.\n\n"); |
michael@0 | 886 | dump("Full message: " + ex + "\n"); |
michael@0 | 887 | dump("See https://developer.mozilla.org/Mozilla/JavaScript_code_modules/Promise.jsm/Promise\n"); |
michael@0 | 888 | dump("Full stack: " + (("stack" in ex)?ex.stack:"not available") + "\n"); |
michael@0 | 889 | dump("*************************\n"); |
michael@0 | 890 | |
michael@0 | 891 | } |
michael@0 | 892 | |
michael@0 | 893 | // Additionally, reject the next promise. |
michael@0 | 894 | nextStatus = STATUS_REJECTED; |
michael@0 | 895 | nextValue = ex; |
michael@0 | 896 | } |
michael@0 | 897 | |
michael@0 | 898 | // Propagate the newly determined state to the next promise. |
michael@0 | 899 | PromiseWalker.completePromise(this.nextPromise, nextStatus, nextValue); |
michael@0 | 900 | }, |
michael@0 | 901 | }; |