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