Bug 711126 - Utilities for Promise.jsm. r=rcampbell,jwalker
--- 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;
+};