Bug 895185 - Implement Promise.every(); r=paolo sr=mossop
authorTim Taubert <ttaubert@mozilla.com>
Sat, 20 Jul 2013 09:34:10 +0200
changeset 139320 bc59f7e483c5e3404b33a8b83b8506908e1edf40
parent 139317 bf73e10f5e54f9c424a0899b3032e11a6a65d10a
child 139321 0ebbdfbb31ebe78e62fd950f9e485b87d886d43b
push id24987
push userttaubert@mozilla.com
push dateSun, 21 Jul 2013 09:13:11 +0000
treeherdermozilla-central@2268ff80683a [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
reviewerspaolo, mossop
bugs895185
milestone25.0a1
first release with
nightly linux32
nightly linux64
nightly mac
nightly win32
nightly win64
last release without
nightly linux32
nightly linux64
nightly mac
nightly win32
nightly win64
Bug 895185 - Implement Promise.every(); r=paolo sr=mossop
toolkit/modules/Promise.jsm
toolkit/modules/tests/xpcshell/test_Promise.js
--- a/toolkit/modules/Promise.jsm
+++ b/toolkit/modules/Promise.jsm
@@ -167,16 +167,70 @@ this.Promise = Object.freeze({
    *       equal to the rejected promise itself, and not its rejection reason.
    */
   reject: function (aReason)
   {
     let promise = new PromiseImpl();
     PromiseWalker.completePromise(promise, STATUS_REJECTED, aReason);
     return promise;
   },
+
+  /**
+   * Returns a promise that is resolved or rejected when all values are
+   * resolved or any is rejected.
+   *
+   * @param aValues
+   *        Array of promises that may be pending, resolved, or rejected. When
+   *        all are resolved or any is rejected, the returned promise will be
+   *        resolved or rejected as well.
+   *
+   * @return A new promise that is fulfilled when all values are resolved or
+   *         that is rejected when any of the values are rejected. Its
+   *         resolution value will be an array of all resolved values in the
+   *         given order, or undefined if aValues is an empty array. The reject
+   *         reason will be forwarded from the first promise in the list of
+   *         given promises to be rejected.
+   */
+  every: function (aValues)
+  {
+    if (!Array.isArray(aValues)) {
+      throw new Error("Promise.every() expects an array of promises or values.");
+    }
+
+    if (!aValues.length) {
+      return Promise.resolve(undefined);
+    }
+
+    let countdown = aValues.length;
+    let deferred = Promise.defer();
+    let resolutionValues = new Array(countdown);
+
+    function checkForCompletion(aValue, aIndex) {
+      resolutionValues[aIndex] = aValue;
+
+      if (--countdown === 0) {
+        deferred.resolve(resolutionValues);
+      }
+    }
+
+    for (let i = 0; i < aValues.length; i++) {
+      let index = i;
+      let value = aValues[i];
+      let resolve = val => checkForCompletion(val, index);
+
+      if (value && typeof(value.then) == "function") {
+        value.then(resolve, deferred.reject);
+      } else {
+        // Given value is not a promise, forward it as a resolution value.
+        resolve(value);
+      }
+    }
+
+    return deferred.promise;
+  },
 });
 
 ////////////////////////////////////////////////////////////////////////////////
 //// PromiseWalker
 
 /**
  * This singleton object invokes the handlers registered on resolved and
  * rejected promises, ensuring that processing is not recursive and is done in
--- a/toolkit/modules/tests/xpcshell/test_Promise.js
+++ b/toolkit/modules/tests/xpcshell/test_Promise.js
@@ -3,18 +3,16 @@
 "use strict";
 
 Components.utils.import("resource://gre/modules/Promise.jsm");
 
 ////////////////////////////////////////////////////////////////////////////////
 //// Test runner
 
 let run_promise_tests = function run_promise_tests(tests, cb) {
-  let timer = Components.classes["@mozilla.org/timer;1"]
-     .createInstance(Components.interfaces.nsITimer);
   let loop = function loop(index) {
     if (index >= tests.length) {
       if (cb) {
         cb.call();
       }
       return;
     }
     do_print("Launching test " + (index + 1) + "/" + tests.length);
@@ -580,13 +578,91 @@ tests.push(
       }
     );
 
     source.resolve("");
 
     return promise;
   }));
 
+// Test that the values of the promise return by Promise.every() are kept in the
+// given order even if the given promises are resolved in arbitrary order
+tests.push(
+  make_promise_test(function every_resolve(test) {
+    let d1 = Promise.defer();
+    let d2 = Promise.defer();
+    let d3 = Promise.defer();
+
+    d3.resolve(4);
+    d2.resolve(2);
+    do_execute_soon(() => d1.resolve(1));
+
+    let promises = [d1.promise, d2.promise, 3, d3.promise];
+
+    return Promise.every(promises).then(
+      function onResolve([val1, val2, val3, val4]) {
+        do_check_eq(val1, 1);
+        do_check_eq(val2, 2);
+        do_check_eq(val3, 3);
+        do_check_eq(val4, 4);
+      }
+    );
+  }));
+
+// Test that rejecting one of the promises passed to Promise.every()
+// rejects the promise return by Promise.every()
+tests.push(
+  make_promise_test(function every_reject(test) {
+    let error = new Error("Boom");
+
+    let d1 = Promise.defer();
+    let d2 = Promise.defer();
+    let d3 = Promise.defer();
+
+    d3.resolve(3);
+    d2.resolve(2);
+    do_execute_soon(() => d1.reject(error));
+
+    let promises = [d1.promise, d2.promise, d3.promise];
+
+    return Promise.every(promises).then(
+      function onResolve() {
+        do_throw("Incorrect call to onResolve listener");
+      },
+      function onReject(reason) {
+        do_check_eq(reason, error, "Rejection lead to the expected reason");
+      }
+    );
+  }));
+
+// Test that passing only values (not promises) to Promise.every()
+// forwards them all as resolution values.
+tests.push(
+  make_promise_test(function every_resolve_no_promises(test) {
+    try {
+      Promise.every(null);
+      do_check_true(false, "every() should only accept arrays.");
+    } catch (e) {
+      do_check_true(true, "every() fails when first the arg is not an array.");
+    }
+
+    let p1 = Promise.every([]).then(
+      function onResolve(val) {
+        do_check_eq(typeof(val), "undefined");
+      }
+    );
+
+    let p2 = Promise.every([1, 2, 3]).then(
+      function onResolve([val1, val2, val3]) {
+        do_check_eq(val1, 1);
+        do_check_eq(val2, 2);
+        do_check_eq(val3, 3);
+      }
+    );
+
+    return Promise.every([p1, p2]);
+  }));
+
 function run_test()
 {
   do_test_pending();
   run_promise_tests(tests, do_test_finished);
 }