Bug 711126 - Utilities for Promise.jsm. r=rcampbell,jwalker
authorDavid Rajchenbach-Teller <dteller@mozilla.com>
Wed, 14 Mar 2012 18:17:43 +0100
changeset 89405 0975cabb34e60de276032b29db11165ee3d4474e
parent 89404 25af1ffbabcee6653351d186d83489030483daa9
child 89406 41d14a8398a208ca8cd725bf20c0297036094047
push id1
push userroot
push dateMon, 20 Oct 2014 17:29:22 +0000
reviewersrcampbell, jwalker
bugs711126
milestone14.0a1
Bug 711126 - Utilities for Promise.jsm. r=rcampbell,jwalker
browser/devtools/shared/Promise.jsm
--- a/browser/devtools/shared/Promise.jsm
+++ b/browser/devtools/shared/Promise.jsm
@@ -135,20 +135,18 @@ Promise.prototype.reject = function(data
 
 /**
  * Internal method to be called on resolve() or reject()
  * @private
  */
 Promise.prototype._complete = function(list, status, data, name) {
   // Complain if we've already been completed
   if (this._status != Promise.PENDING) {
-    if (typeof 'console' === 'object') {
-      console.error('Promise complete. Attempted ' + name + '() with ', data);
-      console.error('Prev status = ', this._status, ', value = ', this._value);
-    }
+    Promise._error("Promise complete.", "Attempted ", name, "() with ", data);
+    Promise._error("Previous status: ", this._status, ", value =", this._value);
     throw new Error('Promise already complete');
   }
 
   this._status = status;
   this._value = data;
 
   // Call all the handlers, and then delete them
   list.forEach(function(handler) {
@@ -168,16 +166,43 @@ Promise.prototype._complete = function(l
     Promise._recent.shift();
   }
   */
 
   return this;
 };
 
 /**
+ * Log an error on the most appropriate channel.
+ *
+ * If the console is available, this method uses |console.warn|. Otherwise,
+ * this method falls back to |dump|.
+ *
+ * @param {...*} items Items to log.
+ */
+Promise._error = null;
+if (typeof console != "undefined" && console.warn) {
+  Promise._error = function() {
+    var args = Array.prototype.slice.call(arguments);
+    args.unshift("Promise");
+    console.warn.call(console, args);
+  };
+} else {
+  Promise._error = function() {
+    var i;
+    var len = arguments.length;
+    dump("Promise: ");
+    for (i = 0; i < len; ++i) {
+      dump(arguments[i]+" ");
+    }
+    dump("\n");
+  };
+}
+
+/**
  * Takes an array of promises and returns a promise that that is fulfilled once
  * all the promises in the array are fulfilled
  * @param promiseList The array of promises
  * @return the promise that is fulfilled when all the array is fulfilled
  */
 Promise.group = function(promiseList) {
   if (!Array.isArray(promiseList)) {
     promiseList = Array.prototype.slice.call(arguments);
@@ -208,8 +233,91 @@ Promise.group = function(promiseList) {
   promiseList.forEach(function(promise, index) {
     var onSuccess = onSuccessFactory(index);
     var onError = groupPromise.reject.bind(groupPromise);
     promise.then(onSuccess, onError);
   });
 
   return groupPromise;
 };
+
+/**
+ * Trap errors.
+ *
+ * This function serves as an asynchronous counterpart to |catch|.
+ *
+ * Example:
+ *  myPromise.chainPromise(a) //May reject
+ *           .chainPromise(b) //May reject
+ *           .chainPromise(c) //May reject
+ *           .trap(d)       //Catch any rejection from a, b or c
+ *           .chainPromise(e) //If either a, b and c or
+ *                            //d has resolved, execute
+ *
+ * Scenario 1:
+ *   If a, b, c resolve, e is executed as if d had not been added.
+ *
+ * Scenario 2:
+ *   If a, b or c rejects, d is executed. If d resolves, we proceed
+ *   with e as if nothing had happened. Otherwise, we proceed with
+ *   the rejection of d.
+ *
+ * @param {Function} aTrap Called if |this| promise is rejected,
+ *   with one argument: the rejection.
+ * @return {Promise} A new promise. This promise resolves if all
+ *   previous promises have resolved or if |aTrap| succeeds.
+ */
+Promise.prototype.trap = function(aTrap) {
+  var promise = new Promise();
+  var resolve = Promise.prototype.resolve.bind(promise);
+  var reject = function(aRejection) {
+    try {
+      //Attempt to handle issue
+      var result = aTrap.call(aTrap, aRejection);
+      promise.resolve(result);
+    } catch (x) {
+      promise.reject(x);
+    }
+  };
+  this.then(resolve, reject);
+  return promise;
+};
+
+/**
+ * Execute regardless of errors.
+ *
+ * This function serves as an asynchronous counterpart to |finally|.
+ *
+ * Example:
+ *  myPromise.chainPromise(a) //May reject
+ *           .chainPromise(b) //May reject
+ *           .chainPromise(c) //May reject
+ *           .always(d)       //Executed regardless
+ *           .chainPromise(e)
+ *
+ * Whether |a|, |b| or |c| resolve or reject, |d| is executed.
+ *
+ * @param {Function} aTrap Called regardless of whether |this|
+ *   succeeds or fails.
+ * @return {Promise} A new promise. This promise holds the same
+ *   resolution/rejection as |this|.
+ */
+Promise.prototype.always = function(aTrap) {
+  var promise = new Promise();
+  var resolve = function(result) {
+    try {
+      aTrap.call(aTrap);
+      promise.resolve(result);
+    } catch (x) {
+      promise.reject(x);
+    }
+  };
+  var reject = function(result) {
+    try {
+      aTrap.call(aTrap);
+      promise.reject(result);
+    } catch (x) {
+      promise.reject(result);
+    }
+  };
+  this.then(resolve, reject);
+  return promise;
+};