Bug 974065 - Add async function helpers to devtools. r=fitzgen, r=robcee
authorBrandon Benvie <bbenvie@mozilla.com>
Wed, 19 Feb 2014 14:08:36 -0800
changeset 169651 9a30d2ed15978b06f5cb78c5fa3551f1f3e70870
parent 169650 547342061dc33fe675b0c77247e995eb80fbe6dd
child 169652 671b3044b16682b89c7aae16f97cc813780c91b2
push id1
push userroot
push dateMon, 20 Oct 2014 17:29:22 +0000
reviewersfitzgen, robcee
bugs974065
milestone30.0a1
Bug 974065 - Add async function helpers to devtools. r=fitzgen, r=robcee
toolkit/devtools/Loader.jsm
toolkit/devtools/async-utils.js
toolkit/devtools/tests/unit/xpcshell.ini
--- a/toolkit/devtools/Loader.jsm
+++ b/toolkit/devtools/Loader.jsm
@@ -62,16 +62,17 @@ BuiltinProvider.prototype = {
         "devtools/toolkit/webconsole": "resource://gre/modules/devtools/toolkit/webconsole",
         "devtools/app-actor-front": "resource://gre/modules/devtools/app-actor-front.js",
         "devtools/styleinspector/css-logic": "resource://gre/modules/devtools/styleinspector/css-logic",
         "devtools/css-color": "resource://gre/modules/devtools/css-color",
         "devtools/output-parser": "resource://gre/modules/devtools/output-parser",
         "devtools/touch-events": "resource://gre/modules/devtools/touch-events",
         "devtools/client": "resource://gre/modules/devtools/client",
         "devtools/pretty-fast": "resource://gre/modules/devtools/pretty-fast.js",
+        "devtools/async-utils": "resource://gre/modules/devtools/async-utils",
 
         "acorn": "resource://gre/modules/devtools/acorn",
         "acorn/util/walk": "resource://gre/modules/devtools/acorn/walk.js",
 
         // Allow access to xpcshell test items from the loader.
         "xpcshell-test": "resource://test"
       },
       globals: loaderGlobals,
@@ -109,16 +110,17 @@ SrcdirProvider.prototype = {
     let webconsoleURI = this.fileURI(OS.Path.join(toolkitDir, "webconsole"));
     let appActorURI = this.fileURI(OS.Path.join(toolkitDir, "apps", "app-actor-front.js"));
     let cssLogicURI = this.fileURI(OS.Path.join(toolkitDir, "styleinspector", "css-logic"));
     let cssColorURI = this.fileURI(OS.Path.join(toolkitDir, "css-color"));
     let outputParserURI = this.fileURI(OS.Path.join(toolkitDir, "output-parser"));
     let touchEventsURI = this.fileURI(OS.Path.join(toolkitDir, "touch-events"));
     let clientURI = this.fileURI(OS.Path.join(toolkitDir, "client"));
     let prettyFastURI = this.fileURI(OS.Path.join(toolkitDir), "pretty-fast.js");
+    let asyncUtilsURI = this.fileURI(OS.Path.join(toolkitDir), "async-utils.js");
     let acornURI = this.fileURI(OS.Path.join(toolkitDir, "acorn"));
     let acornWalkURI = OS.Path.join(acornURI, "walk.js");
     this.loader = new loader.Loader({
       modules: {
         "toolkit/loader": loader,
         "source-map": SourceMap,
       },
       paths: {
@@ -129,16 +131,17 @@ SrcdirProvider.prototype = {
         "devtools/toolkit/webconsole": webconsoleURI,
         "devtools/app-actor-front": appActorURI,
         "devtools/styleinspector/css-logic": cssLogicURI,
         "devtools/css-color": cssColorURI,
         "devtools/output-parser": outputParserURI,
         "devtools/touch-events": touchEventsURI,
         "devtools/client": clientURI,
         "devtools/pretty-fast": prettyFastURI,
+        "devtools/async-utils": asyncUtilsURI,
 
         "acorn": acornURI,
         "acorn/util/walk": acornWalkURI
       },
       globals: loaderGlobals,
       invisibleToDebugger: this.invisibleToDebugger
     });
 
new file mode 100644
--- /dev/null
+++ b/toolkit/devtools/async-utils.js
@@ -0,0 +1,100 @@
+/* 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";
+
+/**
+ * Helpers for async functions. Async functions are generator functions that are
+ * run by Tasks. An async function returns a Promise for the resolution of the
+ * function. When the function returns, the promise is resolved with the
+ * returned value. If it throws the promise rejects with the thrown error.
+ *
+ * See Task documentation at https://developer.mozilla.org/en-US/docs/Mozilla/JavaScript_code_modules/Task.jsm.
+ */
+
+let {Cu} = require("chrome");
+let {Task} = require("resource://gre/modules/Task.jsm");
+let {Promise} = require("resource://gre/modules/Promise.jsm");
+
+/**
+ * Create an async function from a generator function.
+ *
+ * @param Function func
+ *        The generator function that to wrap as an async function.
+ * @return Function
+ *         The async function.
+ */
+exports.async = function async(func) {
+  return function(...args) {
+    return Task.spawn(func.apply(this, args));
+  };
+};
+
+/**
+ * Create an async function that only executes once per instance of an object.
+ * Once called on a given object, the same promise will be returned for any
+ * future calls for that object.
+ *
+ * @param Function func
+ *        The generator function that to wrap as an async function.
+ * @return Function
+ *         The async function.
+ */
+exports.asyncOnce = function asyncOnce(func) {
+  const promises = new WeakMap();
+  return function(...args) {
+    let promise = promises.get(this);
+    if (!promise) {
+      promise = Task.spawn(func.apply(this, args));
+      promises.set(this, promise);
+    }
+    return promise;
+  };
+};
+
+
+/**
+ * Call a function that expects a callback as the last argument and returns a
+ * promise for the result. This simplifies using callback APIs from tasks and
+ * async functions.
+ *
+ * @param Any obj
+ *        The |this| value to call the function on.
+ * @param Function func
+ *        The callback-expecting function to call.
+ * @param Array args
+ *        Additional arguments to pass to the method.
+ * @return Promise
+ *         The promise for the result. If the callback is called with only one
+ *         argument, it is used as the resolution value. If there's multiple
+ *         arguments, an array containing the arguments is the resolution value.
+ *         If the method throws, the promise is rejected with the thrown value.
+ */
+function promisify(obj, func, args) {
+  return new Promise(resolve => {
+    args.push((...results) => {
+      resolve(results.length > 1 ? results : results[0]);
+    });
+    func.apply(obj, args);
+  });
+}
+
+/**
+ * Call a method that expects a callback as the last argument and returns a
+ * promise for the result.
+ *
+ * @see promisify
+ */
+exports.promiseInvoke = function promiseInvoke(obj, func, ...args) {
+  return promisify(obj, func, args);
+};
+
+/**
+ * Call a function that expects a callback as the last argument.
+ *
+ * @see promisify
+ */
+exports.promiseCall = function promiseCall(func, ...args) {
+  return promisify(undefined, func, args);
+};
--- a/toolkit/devtools/tests/unit/xpcshell.ini
+++ b/toolkit/devtools/tests/unit/xpcshell.ini
@@ -1,8 +1,9 @@
 [DEFAULT]
 head = head_devtools.js
 tail =
 
 [test_independent_loaders.js]
 [test_invisible_loader.js]
 [test_safeErrorString.js]
 [test_defineLazyPrototypeGetter.js]
+[test_async-utils.js]