Bug 1080466 - Implement resolveOrTimeout in PromiseUtils.jsm. r=yoric
authorAkshendra Pratap <akshendra521994@gmail.com>
Thu, 30 Oct 2014 08:21:00 -0400
changeset 213965 26bc4268f868ccca19336a56bae4663153649aee
parent 213964 d64ba06f84721f85777a69aa612f94b0729f5745
child 213966 92073635b516f8e522ab0f494ffc9df3fcec6309
push id27769
push userkwierso@gmail.com
push dateWed, 05 Nov 2014 03:53:35 +0000
treeherdermozilla-central@62990ec7ad78 [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
reviewersyoric
bugs1080466
milestone36.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 1080466 - Implement resolveOrTimeout in PromiseUtils.jsm. r=yoric
toolkit/modules/PromiseUtils.jsm
toolkit/modules/moz.build
toolkit/modules/tests/xpcshell/test_PromiseUtils.js
toolkit/modules/tests/xpcshell/xpcshell.ini
new file mode 100644
--- /dev/null
+++ b/toolkit/modules/PromiseUtils.jsm
@@ -0,0 +1,54 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+"use strict"
+
+this.EXPORTED_SYMBOLS = ["PromiseUtils"];
+
+Components.utils.import("resource://gre/modules/Timer.jsm");
+
+this.PromiseUtils = {
+  /*
+   * A simple timeout mechanism.
+   *
+   * Example:
+   * resolveOrTimeout(myModule.shutdown(), 1000, new Error("The module took too long to shutdown"));
+   *
+   * @param {Promise} promise The Promise that should resolve/reject quickly.
+   * @param {number} delay A delay after which to stop waiting for `promise`, in milliseconds.
+   * @param {function} rejection If `promise` hasn't resolved/rejected after `delay`,
+   * a value used to construct the rejection.
+   *
+   * @return {Promise} A promise that behaves as `promise`, if `promise` is
+   * resolved/rejected within `delay` ms, or rejects with `rejection()` otherwise.
+   */
+  resolveOrTimeout : function(promise, delay, rejection)  {
+    // throw a TypeError if <promise> is not a Promise object
+    if (!(promise instanceof Promise)) {
+      throw new TypeError("first argument <promise> must be a Promise object");
+    }
+
+    // throw a TypeError if <delay> is not a number
+    if (typeof delay != "number" || delay < 0) {
+      throw new TypeError("second argument <delay> must be a positive number");
+    }
+
+    // throws a TypeError if <rejection> is not a function
+    if (rejection && typeof rejection != "function") {
+      throw new TypeError("third optional argument <rejection> must be a function");
+    }
+
+    return new Promise((resolve, reject) => {
+      promise.then(resolve, reject);
+      let id = setTimeout(() => {
+        try {
+          rejection ? reject(rejection()) : reject(new Error("Promise Timeout"));
+        } catch(ex) {
+          reject(ex);
+        }
+        clearTimeout(id);
+      }, delay);
+    });
+  }
+}
\ No newline at end of file
--- a/toolkit/modules/moz.build
+++ b/toolkit/modules/moz.build
@@ -31,16 +31,17 @@ EXTRA_JS_MODULES += [
     'NewTabUtils.jsm',
     'PageMenu.jsm',
     'PermissionsUtils.jsm',
     'PopupNotifications.jsm',
     'Preferences.jsm',
     'PrivateBrowsingUtils.jsm',
     'Promise-backend.js',
     'Promise.jsm',
+    'PromiseUtils.jsm',
     'PropertyListUtils.jsm',
     'RemoteController.jsm',
     'RemoteFinder.jsm',
     'RemoteSecurityUI.jsm',
     'RemoteWebNavigation.jsm',
     'RemoteWebProgress.jsm',
     'SelectContentHelper.jsm',
     'SelectParentHelper.jsm',
new file mode 100644
--- /dev/null
+++ b/toolkit/modules/tests/xpcshell/test_PromiseUtils.js
@@ -0,0 +1,78 @@
+  /* Any copyright is dedicated to the Public Domain.
+ * http://creativecommons.org/publicdomain/zero/1.0/ */
+
+"use strict";
+
+Components.utils.import("resource://gre/modules/PromiseUtils.jsm");
+Components.utils.import("resource://gre/modules/Timer.jsm");
+
+// Tests for PromiseUtils.jsm
+function run_test() {
+  run_next_test();
+}
+
+/* Tests for the case when arguments to resolveOrTimeout
+ * are not of correct type */
+add_task(function* test_wrong_arguments() {
+  let p = new Promise((resolve, reject) => {});
+  // for the first argument
+  Assert.throws(() => PromiseUtils.resolveOrTimeout("string", 200), /first argument <promise> must be a Promise object/,
+                "TypeError thrown because first argument is not a Promsie object");
+  // for second argument
+  Assert.throws(() => PromiseUtils.resolveOrTimeout(p, "string"), /second argument <delay> must be a positive number/,
+                "TypeError thrown because second argument is not a positive number");
+  // for the third argument
+  Assert.throws(() => PromiseUtils.resolveOrTimeout(p, 200, "string"), /third optional argument <rejection> must be a function/,
+                "TypeError thrown because thrird argument is not a function");
+});
+
+/* Tests for the case when the optional third argument is not provided
+ * In that case the returned promise rejects with a default Error */
+add_task(function* test_optional_third_argument() {
+  let p = new Promise((resolve, reject) => {});
+  yield Assert.rejects(PromiseUtils.resolveOrTimeout(p, 200), /Promise Timeout/, "Promise rejects with a default Error");
+});
+
+/* Test for the case when the passed promise resolves quickly
+ * In that case the returned promise also resolves with the same value */
+add_task(function* test_resolve_quickly() {
+  let p = new Promise((resolve, reject) => setTimeout(() => resolve("Promise is resolved"), 20));
+  let result = yield PromiseUtils.resolveOrTimeout(p, 200);
+  Assert.equal(result, "Promise is resolved", "Promise resolves quickly");
+});
+
+/* Test for the case when the passed promise rejects quickly
+ * In that case the returned promise also rejects with the same value */
+add_task(function* test_reject_quickly() {
+  let p = new Promise((resolve, reject) => setTimeout(() => reject("Promise is rejected"), 20));
+  yield Assert.rejects(PromiseUtils.resolveOrTimeout(p, 200), /Promise is rejected/, "Promise rejects quickly");
+});
+
+/* Tests for the case when the passed promise doesn't settle
+ * and rejection returns string/object/undefined */
+add_task(function* test_rejection_function() {
+  let p = new Promise((resolve, reject) => {});
+  // for rejection returning string
+  yield Assert.rejects(PromiseUtils.resolveOrTimeout(p, 200, () => {
+    return "Rejection returned a string";
+  }), /Rejection returned a string/, "Rejection returned a string");
+
+  // for rejection returning object
+  yield Assert.rejects(PromiseUtils.resolveOrTimeout(p, 200, () => {
+    return {Name:"Promise"};
+  }), Object, "Rejection returned an object");
+
+  // for rejection returning undefined
+  yield Assert.rejects(PromiseUtils.resolveOrTimeout(p, 200, () => {
+    return;
+  }), undefined, "Rejection returned undefined");
+});
+
+/* Tests for the case when the passed promise doesn't settles
+ * and rejection throws an error */
+add_task(function* test_rejection_throw_error() {
+  let p = new Promise((resolve, reject) => {});
+  yield Assert.rejects(PromiseUtils.resolveOrTimeout(p, 200, () => {
+    throw new Error("Rejection threw an Error");
+  }), /Rejection threw an Error/, "Rejection threw an error");
+});
\ No newline at end of file
--- a/toolkit/modules/tests/xpcshell/xpcshell.ini
+++ b/toolkit/modules/tests/xpcshell/xpcshell.ini
@@ -16,16 +16,17 @@ support-files =
 # GMPInstallManager is not shipped on Android
 skip-if = os == 'android'
 [test_Http.js]
 [test_Log.js]
 [test_NewTabUtils.js]
 [test_PermissionsUtils.js]
 [test_Preferences.js]
 [test_Promise.js]
+[test_PromiseUtils.js]
 [test_propertyListsUtils.js]
 [test_readCertPrefs.js]
 [test_Services.js]
 [test_sqlite.js]
 [test_sqlite_shutdown.js]
 [test_task.js]
 [test_TelemetryTimestamps.js]
 [test_timer.js]