michael@0: /* This Source Code Form is subject to the terms of the Mozilla Public michael@0: * License, v. 2.0. If a copy of the MPL was not distributed with this michael@0: * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ michael@0: michael@0: "use strict"; michael@0: michael@0: /** michael@0: * Helpers for async functions. Async functions are generator functions that are michael@0: * run by Tasks. An async function returns a Promise for the resolution of the michael@0: * function. When the function returns, the promise is resolved with the michael@0: * returned value. If it throws the promise rejects with the thrown error. michael@0: * michael@0: * See Task documentation at https://developer.mozilla.org/en-US/docs/Mozilla/JavaScript_code_modules/Task.jsm. michael@0: */ michael@0: michael@0: let {Cu} = require("chrome"); michael@0: let {Task} = require("resource://gre/modules/Task.jsm"); michael@0: let {Promise} = require("resource://gre/modules/Promise.jsm"); michael@0: michael@0: /** michael@0: * Create an async function from a generator function. michael@0: * michael@0: * @param Function func michael@0: * The generator function that to wrap as an async function. michael@0: * @return Function michael@0: * The async function. michael@0: */ michael@0: exports.async = function async(func) { michael@0: return function(...args) { michael@0: return Task.spawn(func.apply(this, args)); michael@0: }; michael@0: }; michael@0: michael@0: /** michael@0: * Create an async function that only executes once per instance of an object. michael@0: * Once called on a given object, the same promise will be returned for any michael@0: * future calls for that object. michael@0: * michael@0: * @param Function func michael@0: * The generator function that to wrap as an async function. michael@0: * @return Function michael@0: * The async function. michael@0: */ michael@0: exports.asyncOnce = function asyncOnce(func) { michael@0: const promises = new WeakMap(); michael@0: return function(...args) { michael@0: let promise = promises.get(this); michael@0: if (!promise) { michael@0: promise = Task.spawn(func.apply(this, args)); michael@0: promises.set(this, promise); michael@0: } michael@0: return promise; michael@0: }; michael@0: }; michael@0: michael@0: michael@0: /** michael@0: * Call a function that expects a callback as the last argument and returns a michael@0: * promise for the result. This simplifies using callback APIs from tasks and michael@0: * async functions. michael@0: * michael@0: * @param Any obj michael@0: * The |this| value to call the function on. michael@0: * @param Function func michael@0: * The callback-expecting function to call. michael@0: * @param Array args michael@0: * Additional arguments to pass to the method. michael@0: * @return Promise michael@0: * The promise for the result. If the callback is called with only one michael@0: * argument, it is used as the resolution value. If there's multiple michael@0: * arguments, an array containing the arguments is the resolution value. michael@0: * If the method throws, the promise is rejected with the thrown value. michael@0: */ michael@0: function promisify(obj, func, args) { michael@0: return new Promise(resolve => { michael@0: args.push((...results) => { michael@0: resolve(results.length > 1 ? results : results[0]); michael@0: }); michael@0: func.apply(obj, args); michael@0: }); michael@0: } michael@0: michael@0: /** michael@0: * Call a method that expects a callback as the last argument and returns a michael@0: * promise for the result. michael@0: * michael@0: * @see promisify michael@0: */ michael@0: exports.promiseInvoke = function promiseInvoke(obj, func, ...args) { michael@0: return promisify(obj, func, args); michael@0: }; michael@0: michael@0: /** michael@0: * Call a function that expects a callback as the last argument. michael@0: * michael@0: * @see promisify michael@0: */ michael@0: exports.promiseCall = function promiseCall(func, ...args) { michael@0: return promisify(undefined, func, args); michael@0: };