toolkit/modules/Promise-backend.js

Wed, 31 Dec 2014 06:09:35 +0100

author
Michael Schloh von Bennewitz <michael@schloh.com>
date
Wed, 31 Dec 2014 06:09:35 +0100
changeset 0
6474c204b198
permissions
-rwxr-xr-x

Cloned upstream origin tor-browser at tor-browser-31.3.0esr-4.5-1-build1
revision ID fc1c9ff7c1b2defdbc039f12214767608f46423f for hacking purpose.

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

mercurial