Bug 1265796 - Add a queueAsyncCalls utility function; draft
authorGreg Tatum <tatum.creative@gmail.com>
Wed, 17 Aug 2016 10:07:42 -0500
changeset 409216 f055a214952efa973575f8b207fe0b884ca60228
parent 409215 bd1d03aa536635fdf7769f465b3b82615ebe1ba1
child 409217 6e210d44672518d38ed41772d5b0359d70923135
push id28433
push userbmo:gtatum@mozilla.com
push dateFri, 02 Sep 2016 15:08:28 +0000
bugs1265796
milestone51.0a1
Bug 1265796 - Add a queueAsyncCalls utility function; While adding async code to previously synchronous callstacks, it is useful to put function calls into a queue so that they maintain some of their synchronous nature. MozReview-Commit-ID: 3F4jULzJsS6
devtools/shared/ThreadSafeDevToolsUtils.js
devtools/shared/tests/unit/test_queueAsyncCalls.js
devtools/shared/tests/unit/xpcshell.ini
--- a/devtools/shared/ThreadSafeDevToolsUtils.js
+++ b/devtools/shared/ThreadSafeDevToolsUtils.js
@@ -327,8 +327,33 @@ exports.settleAll = values => {
         value.then(resolver, rejecter);
       } else {
         // Given value is not a promise, forward it as a resolution value.
         resolver(value);
       }
     }
   });
 };
+
+/**
+ * Queue asynchronous function calls, so that each function call's returned promise
+ * must resolve before executing the next one. This guard was created for dealing with
+ * race conditions when turning existing synchronous callstacks into asynchronous ones.
+ *
+ * Note: The queue is continued to be processed even if a previous call's returnd promise
+ * is rejected.
+ *
+ * @param {Function} fn - The function to queue, should return a promise, but it isn't
+                          required.
+ * @param {Object} context - The optional context to apply to the function.
+ *
+ * @return {Function} - The queue guarded function.
+ */
+exports.queueAsyncCalls = function (fn, context) {
+  let promise = Promise.resolve();
+
+  return (...args) => {
+    promise = promise.then(null, () => {}).then(() => {
+      return fn.apply(context, args);
+    });
+    return promise;
+  };
+}
new file mode 100644
--- /dev/null
+++ b/devtools/shared/tests/unit/test_queueAsyncCalls.js
@@ -0,0 +1,63 @@
+/* Any copyright is dedicated to the Public Domain.
+   http://creativecommons.org/publicdomain/zero/1.0/ */
+
+"use strict";
+
+// Test ThreadSafeDevToolsUtils.queueAsyncCalls
+const promise = require("promise");
+
+function run_test() {
+  const { queueAsyncCalls } = DevToolsUtils;
+
+  function QueueChecker() {
+    this.letters = [];
+    this.addLetter = queueAsyncCalls(this.addLetter, this);
+  }
+
+  QueueChecker.prototype = {
+    addLetter(letter, resolved) {
+      return resolved.then(() => this.letters.push(letter));
+    }
+  };
+
+  const queueChecker = new QueueChecker();
+
+  const deferA = promise.defer();
+  const deferB = promise.defer();
+  const deferC = promise.defer();
+  const deferD = promise.defer();
+  const deferE = promise.defer();
+  const deferF = promise.defer();
+  const deferG = promise.defer();
+
+  ok(true, "Calling all the functions in order.");
+
+  const addLetters = [
+    queueChecker.addLetter("a", deferA.promise),
+    queueChecker.addLetter("b", deferB.promise),
+    queueChecker.addLetter("c", deferC.promise),
+    queueChecker.addLetter("d", deferD.promise),
+    queueChecker.addLetter("e", deferE.promise),
+    queueChecker.addLetter("f", deferF.promise),
+    queueChecker.addLetter("g", deferG.promise)
+  ];
+
+  ok(true, "Resolve and reject everything out of order.");
+
+  deferE.resolve();
+  deferB.resolve();
+  deferC.reject();
+  deferA.resolve();
+  deferG.resolve();
+  deferF.reject();
+  deferD.resolve();
+
+  function catchErrors(p) {
+    return p.then(null, () => {});
+  }
+
+  Promise.all(addLetters.map(catchErrors)).then(() => {
+    deepEqual(queueChecker.letters, ["a", "b", "d", "e", "g"],
+              "The letters were added in order.");
+  });
+}
--- a/devtools/shared/tests/unit/xpcshell.ini
+++ b/devtools/shared/tests/unit/xpcshell.ini
@@ -19,14 +19,15 @@ support-files =
 [test_independent_loaders.js]
 [test_invisible_loader.js]
 [test_isSet.js]
 [test_safeErrorString.js]
 [test_defineLazyPrototypeGetter.js]
 [test_async-utils.js]
 [test_console_filtering.js]
 [test_prettifyCSS.js]
+[test_queueAsyncCalls.js]
 [test_require_lazy.js]
 [test_require_raw.js]
 [test_require.js]
 [test_stack.js]
 [test_defer.js]
 [test_executeSoon.js]