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
--- 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]