UNPKG

@qooxdoo/framework

Version:

The JS Framework for Coders

1,333 lines (1,215 loc) 259 kB
/* ************************************************************************ qooxdoo - the new era of web development http://qooxdoo.org Copyright: 2016 Zenesis Limited, http://www.zenesis.com BluebirdJS Copyright (c) 2013-2016 Petka Antonov http://bluebirdjs.com/ License: MIT: https://opensource.org/licenses/MIT See the LICENSE file in the project's top-level directory for details. Authors: * John Spackman (john.spackman@zenesis.com) ************************************************************************ */ /** * This class adds Promise/A+ support to Qooxdoo, as specified at * https://github.com/promises-aplus/promises-spec and using the Bluebird Promise * library (http://bluebirdjs.com/) to implement it. The official Promise/A+ API) * is mirrored exactly, and a number of extension methods are added with the BluebirdJS * API for inspiration (many/most of the extension functions are taken verbatim). * * There are two ways to bind a 'this' value to callbacks - the first is to * append a context method to methods like then(), and the second is to specify * the context as the second parameter to the constructor and all callbacks will * be bound to that value. * * For example: * * <pre class="javascript"> * var promise = new qx.Promise(myAsyncFunction, this); * promise.then(function() { * // 'this' is preserved from the outer scope * }); * * // ... is the same as: ... * var promise = new qx.Promise(myAsyncFunction); * promise.then(function() { * // 'this' is preserved from the outer scope * }, this); * </pre> * * If you have an existing qx.Promise and want to bind all callbacks, use the * bind() method - but note that it returns a new promise: * * <pre class="javascript"> * var promise = someMethodThatReturnsAPromise(); * var boundPromise = promise.bind(this); * boundPromise.then(function() { * // 'this' is preserved from the outer scope * }, this); * </pre> * */ /* @ignore(process.*) @ignore(global.*) @ignore(Symbol.*) @ignore(chrome.*) */ qx.Class.define("qx.Promise", { extend: qx.core.Object, /** * Constructor. * * The promise function is called with two parameters, functions which are to be called * when the promise is fulfilled or rejected respectively. If you do not provide any * parameters, the promise can be externally resolved or rejected by calling the * <code>resolve()</code> or <code>reject()</code> methods. * * @param fn {Function} the promise function called with <code>(resolve, reject)</code> * @param context {Object?} optional context for all callbacks */ construct: function(fn, context) { this.base(arguments); qx.Promise.__initialize(); if (fn instanceof qx.Promise.Bluebird) { this.__p = fn; } else if (fn) { if (context !== undefined && context !== null) { fn = fn.bind(context); } if (qx.core.Environment.get("qx.debug")) { var origFn = fn; var self = this; fn = function(resolve, reject) { return origFn(resolve, function(reason) { var args = qx.lang.Array.fromArguments(arguments); if (reason === undefined) { args.shift(); args.unshift(qx.Promise.__DEFAULT_ERROR); } else if (reason && !(reason instanceof Error)) { self.error("Calling reject with non-error object, createdAt=" + JSON.stringify(self.$$createdAt || null)); } reject.apply(this, args) }); }; } this.__p = new qx.Promise.Bluebird(fn); } else { this.__p = new qx.Promise.Bluebird(this.__externalPromise.bind(this)); } qx.core.Assert.assertTrue(!this.__p.$$qxPromise); this.__p.$$qxPromise = this; if (context !== undefined && context !== null) { this.__p = this.__p.bind(context); } }, /** * Destructor */ destruct: function() { delete this.__p.$$qxPromise; delete this.__p; }, members: { /** The Promise */ __p: null, /** Stores data for completing the promise externally */ __external: null, /* ********************************************************************************* * * Promise API methods * */ /** * Returns a promise which is determined by the functions <code>onFulfilled</code> * and <code>onRejected</code>. * * @param onFulfilled {Function} called when the Promise is fulfilled. This function * has one argument, the fulfillment value. * @param onRejected {Function?} called when the Promise is rejected. This function * has one argument, the rejection reason. * @return {qx.Promise} */ then: function(onFulfilled, onRejected) { return this._callMethod('then', arguments); }, /** * Appends a rejection handler callback to the promise, and returns a new promise * resolving to the return value of the callback if it is called, or to its original * fulfillment value if the promise is instead fulfilled. * * @param onRejected {Function?} called when the Promise is rejected. This function * has one argument, the rejection reason. * @return {qx.Promise} a qx.Promise is rejected if onRejected throws an error or * returns a Promise which is itself rejected; otherwise, it is resolved. */ "catch": function(onRejected) { return this._callMethod('catch', arguments); }, /* ********************************************************************************* * * Extension Promise methods * */ /** * Creates a new qx.Promise with the 'this' set to a different context * * @param context {Object} the 'this' context for the new Promise * @return {qx.Promise} the new promise */ bind: function(context) { return qx.Promise.__wrap(this.__p.bind(context)); }, /** * Like calling <code>.then</code>, but the fulfillment value must be an array, which is flattened * to the formal parameters of the fulfillment handler. * * For example: * <pre> * qx.Promise.all([ * fs.readFileAsync("file1.txt"), * fs.readFileAsync("file2.txt") * ]).spread(function(file1text, file2text) { * if (file1text === file2text) { * console.log("files are equal"); * } * else { * console.log("files are not equal"); * } * }); * </pre> * * @param fulfilledHandler {Function} called when the Promises are fulfilled. * @return {qx.Promise} */ spread: function(fulfilledHandler) { return this._callMethod('spread', arguments); }, /** * Appends a handler that will be called regardless of this promise's fate. The handler * is not allowed to modify the value of the promise * * @param handler {Function?} called when the Promise is fulfilled or rejected. This function * has no arguments, but can return a promise * @return {qx.Promise} a qx.Promise chained from this promise */ "finally": function(onRejected) { return this._callMethod('finally', arguments); }, /** * Cancel this promise. Will not do anything if this promise is already settled. */ cancel: function() { return this._callMethod('cancel', arguments); }, /** * Same as {@link qx.Promise.all} except that it iterates over the value of this promise, when * it is fulfilled; for example, if this Promise resolves to an Iterable (eg an Array), * <code>.all</code> will return a Promise that waits for all promises in that Iterable to be * fullfilled. The Iterable can be a mix of values and Promises * * @return {qx.Promise} */ all: function() { return this._callIterableMethod('all', arguments); }, /** * Same as {@link qx.Promise.race} except that it iterates over the value of this promise, when * it is fulfilled; for example, if this Promise resolves to an Iterable (eg an Array), * <code>.race</code> will return a Promise that waits until the first promise in that Iterable * has been fullfilled. The Iterable can be a mix of values and Promises * * @return {qx.Promise} */ race: function(iterable) { return this._callIterableMethod('race', arguments); }, /** * Same as {@link qx.Promise.some} except that it iterates over the value of this promise, when * it is fulfilled. Like <code>some</code>, with 1 as count. However, if the promise fulfills, * the fulfillment value is not an array of 1 but the value directly. * * @return {qx.Promise} */ any: function(iterable) { return this._callIterableMethod('any', arguments); }, /** * Same as {@link qx.Promise.some} except that it iterates over the value of this promise, when * it is fulfilled; return a promise that is fulfilled as soon as count promises are fulfilled * in the array. The fulfillment value is an array with count values in the order they were fulfilled. * * @param iterable {Iterable} An iterable object, such as an Array * @param count {Integer} * @return {qx.Promise} */ some: function(iterable, count) { return this._callIterableMethod('some', arguments); }, /** * Same as {@link qx.Promise.forEach} except that it iterates over the value of this promise, when * it is fulfilled; iterates over the values with the given <code>iterator</code> function with the signature * <code>(value, index, length)</code> where <code>value</code> is the resolved value. Iteration happens * serially. If any promise is rejected the returned promise is rejected as well. * * Resolves to the original array unmodified, this method is meant to be used for side effects. If the iterator * function returns a promise or a thenable, then the result of the promise is awaited, before continuing with * next iteration. * * @param iterable {Iterable} An iterable object, such as an Array * @param iterator {Function} the callback, with <code>(value, index, length)</code> * @return {qx.Promise} */ forEach: function(iterable, iterator) { return this._callIterableMethod('each', arguments); }, /** * Same as {@link qx.Promise.filter} except that it iterates over the value of this promise, when it is fulfilled; * iterates over all the values into an array and filter the array to another using the given filterer function. * * @param iterable {Iterable} An iterable object, such as an Array * @param iterator {Function} the callback, with <code>(value, index, length)</code> * @param options {Object?} options; can be: * <code>concurrency</code> max nuber of simultaneous filters, default is <code>Infinity</code> * @return {qx.Promise} */ filter: function(iterable, iterator, options) { return this._callIterableMethod('filter', arguments); }, /** * Same as {@link qx.Promise.map} except that it iterates over the value of this promise, when it is fulfilled; * iterates over all the values into an array and map the array to another using the given mapper function. * * Promises returned by the mapper function are awaited for and the returned promise doesn't fulfill * until all mapped promises have fulfilled as well. If any promise in the array is rejected, or * any promise returned by the mapper function is rejected, the returned promise is rejected as well. * * The mapper function for a given item is called as soon as possible, that is, when the promise * for that item's index in the input array is fulfilled. This doesn't mean that the result array * has items in random order, it means that .map can be used for concurrency coordination unlike * .all. * * @param iterable {Iterable} An iterable object, such as an Array * @param iterator {Function} the callback, with <code>(value, index, length)</code> * @param options {Object?} options; can be: * <code>concurrency</code> max nuber of simultaneous filters, default is <code>Infinity</code> * @return {qx.Promise} */ map: function(iterable, iterator, options) { return this._callIterableMethod('map', arguments); }, /** * Same as {@link qx.Promise.mapSeries} except that it iterates over the value of this promise, when * it is fulfilled; iterates over all the values into an array and iterate over the array serially, * in-order. * * Returns a promise for an array that contains the values returned by the iterator function in their * respective positions. The iterator won't be called for an item until its previous item, and the * promise returned by the iterator for that item are fulfilled. This results in a mapSeries kind of * utility but it can also be used simply as a side effect iterator similar to Array#forEach. * * If any promise in the input array is rejected or any promise returned by the iterator function is * rejected, the result will be rejected as well. * * @param iterable {Iterable} An iterable object, such as an Array * @param iterator {Function} the callback, with <code>(value, index, length)</code> * @return {qx.Promise} */ mapSeries: function(iterable, iterator) { return this._callIterableMethod('mapSeries', arguments); }, /** * Same as {@link qx.Promise.reduce} except that it iterates over the value of this promise, when * it is fulfilled; iterates over all the values in the <code>Iterable</code> into an array and * reduce the array to a value using the given reducer function. * * If the reducer function returns a promise, then the result of the promise is awaited, before * continuing with next iteration. If any promise in the array is rejected or a promise returned * by the reducer function is rejected, the result is rejected as well. * * If initialValue is undefined (or a promise that resolves to undefined) and the iterable contains * only 1 item, the callback will not be called and the iterable's single item is returned. If the * iterable is empty, the callback will not be called and initialValue is returned (which may be * undefined). * * qx.Promise.reduce will start calling the reducer as soon as possible, this is why you might want to * use it over qx.Promise.all (which awaits for the entire array before you can call Array#reduce on it). * * @param iterable {Iterable} An iterable object, such as an Array * @param reducer {Function} the callback, with <code>(value, index, length)</code> * @param initialValue {Object?} optional initial value * @return {qx.Promise} */ reduce: function(iterable, reducer, initialValue) { return this._callIterableMethod('reduce', arguments); }, /** * External promise handler */ __externalPromise: function(resolve, reject) { this.__external = { resolve: resolve, reject: reject, complete: false }; }, /** * Returns the data stored by __externalPromise, throws an exception once processed */ __getPendingExternal: function() { if (!this.__external) { throw new Error("Promise cannot be resolved externally"); } if (this.__external.complete) { throw new Error("Promise has already been resolved or rejected"); } this.__external.complete = true; return this.__external; }, /** * Resolves an external promise */ resolve: function(value) { this.__getPendingExternal().resolve(value); }, /** * Rejects an external promise */ reject: function(reason) { this.__getPendingExternal().reject(reason); }, /* ********************************************************************************* * * Utility methods * */ /** * Helper method used to call Promise methods which iterate over an array */ _callIterableMethod: function(methodName, args) { args = qx.Promise.__bindArgs(args); return qx.Promise.__wrap(this.__p.then(function(value) { var newP = qx.Promise.Bluebird.resolve(value instanceof qx.data.Array ? value.toArray() : value); return qx.Promise.__wrap(newP[methodName].apply(newP, args)); })); }, /** * Helper method used to call a Promise method */ _callMethod: function(methodName, args) { args = qx.Promise.__bindArgs(args); return qx.Promise.__wrap(this.__p[methodName].apply(this.__p, args)); }, /** * Returns the actual Promise implementation. * * Note that Bluebird is the current implementation, and may change without * notice in the future; if you use this API you accept that this is a private * implementation detail exposed for debugging or diagnosis purposes only. For * this reason, the toPromise() method is listed as deprecated starting from the * first release * @deprecated {6.0} this API method is subject to change */ toPromise: function() { return this.__p; } }, statics: { /** Bluebird Promise library; always available */ Bluebird: null, /** Native Promise library; only available if the browser supports it */ Native: null, /** Promise library, either the Native one or a Polyfill; reliable choice for native Promises */ Promise: null, /** This is used to suppress warnings about rejections without an Error object, only used if * the reason is undefined */ __DEFAULT_ERROR: new Error("Default Error"), /* ********************************************************************************* * * Promise API methods * */ /** * Returns a Promise object that is resolved with the given value. If the value is a thenable (i.e. * has a then method), the returned promise will "follow" that thenable, adopting its eventual * state; otherwise the returned promise will be fulfilled with the value. Generally, if you * don't know if a value is a promise or not, Promise.resolve(value) it instead and work with * the return value as a promise. * * @param value {Object} * @param context {Object?} optional context for callbacks to be bound to * @return {qx.Promise} */ resolve: function(value, context) { var promise; if (value instanceof qx.Promise) { promise = value; } else { promise = qx.Promise.__wrap(qx.Promise.Bluebird.resolve(value)); } if (context !== undefined) { promise = promise.bind(context); } return promise; }, /** * Returns a Promise object that is rejected with the given reason. * @param reason {Object?} Reason why this Promise rejected. A warning is generated if not instanceof Error. If undefined, a default Error is used. * @param context {Object?} optional context for callbacks to be bound to * @return {qx.Promise} */ reject: function(reason, context) { var args = qx.lang.Array.fromArguments(arguments); if (reason === undefined) { args.shift(); args.unshift(qx.Promise.__DEFAULT_ERROR); } else if (!(reason instanceof Error)) { qx.log.Logger.warn("Rejecting a promise with a non-Error value"); } var promise = qx.Promise.__callStaticMethod('reject', args, 0); if (context !== undefined) { promise = promise.bind(context); } return promise; }, /** * Returns a promise that resolves when all of the promises in the object properties have resolved, * or rejects with the reason of the first passed promise that rejects. The result of each property * is placed back in the object, replacing the promise. Note that non-promise values are untouched. * * @param value {var} An object * @return {qx.Promise} */ allOf: function(value) { function action(value) { var arr = []; var names = []; for (var name in value) { if (value.hasOwnProperty(name) && value[name] instanceof qx.Promise) { arr.push(value[name]); names.push(name); } } return qx.Promise.all(arr) .then(function(arr) { arr.forEach(function(item, index) { value[names[index]] = item; }); return value; }); } return value instanceof qx.Promise ? value.then(action) : action(value); }, /** * Returns a promise that resolves when all of the promises in the iterable argument have resolved, * or rejects with the reason of the first passed promise that rejects. Note that non-promise values * are untouched. * * @param iterable {Iterable} An iterable object, such as an Array * @return {qx.Promise} */ all: function(iterable) { return qx.Promise.__callStaticMethod('all', arguments); }, /** * Returns a promise that resolves or rejects as soon as one of the promises in the iterable resolves * or rejects, with the value or reason from that promise. * @param iterable {Iterable} An iterable object, such as an Array * @return {qx.Promise} */ race: function(iterable) { return qx.Promise.__callStaticMethod('race', arguments); }, /* ********************************************************************************* * * Extension API methods * */ /** * Like Promise.some, with 1 as count. However, if the promise fulfills, the fulfillment value is not an * array of 1 but the value directly. * * @param iterable {Iterable} An iterable object, such as an Array * @return {qx.Promise} */ any: function(iterable) { return qx.Promise.__callStaticMethod('any', arguments); }, /** * Given an Iterable (arrays are Iterable), or a promise of an Iterable, which produces promises (or a mix * of promises and values), iterate over all the values in the Iterable into an array and return a promise * that is fulfilled as soon as count promises are fulfilled in the array. The fulfillment value is an * array with count values in the order they were fulfilled. * * @param iterable {Iterable} An iterable object, such as an Array * @param count {Integer} * @return {qx.Promise} */ some: function(iterable, count) { return qx.Promise.__callStaticMethod('some', arguments); }, /** * Iterate over an array, or a promise of an array, which contains promises (or a mix of promises and values) * with the given <code>iterator</code> function with the signature <code>(value, index, length)</code> where * <code>value</code> is the resolved value of a respective promise in the input array. Iteration happens * serially. If any promise in the input array is rejected the returned promise is rejected as well. * * Resolves to the original array unmodified, this method is meant to be used for side effects. If the iterator * function returns a promise or a thenable, then the result of the promise is awaited, before continuing with * next iteration. * * @param iterable {Iterable} An iterable object, such as an Array * @param iterator {Function} the callback, with <code>(value, index, length)</code> * @return {qx.Promise} */ forEach: function(iterable, iterator) { return qx.Promise.__callStaticMethod('each', arguments); }, /** * Given an Iterable(arrays are Iterable), or a promise of an Iterable, which produces promises (or a mix of * promises and values), iterate over all the values in the Iterable into an array and filter the array to * another using the given filterer function. * * It is essentially an efficient shortcut for doing a .map and then Array#filter: * <pre> * qx.Promise.map(valuesToBeFiltered, function(value, index, length) { * return Promise.all([filterer(value, index, length), value]); * }).then(function(values) { * return values.filter(function(stuff) { * return stuff[0] == true * }).map(function(stuff) { * return stuff[1]; * }); * }); * </pre> * * @param iterable {Iterable} An iterable object, such as an Array * @param iterator {Function} the callback, with <code>(value, index, length)</code> * @param options {Object?} options; can be: * <code>concurrency</code> max nuber of simultaneous filters, default is <code>Infinity</code> * @return {qx.Promise} */ filter: function(iterable, iterator, options) { return qx.Promise.__callStaticMethod('filter', arguments); }, /** * Given an <code>Iterable</code> (arrays are <code>Iterable</code>), or a promise of an * <code>Iterable</code>, which produces promises (or a mix of promises and values), iterate over * all the values in the <code>Iterable</code> into an array and map the array to another using * the given mapper function. * * Promises returned by the mapper function are awaited for and the returned promise doesn't fulfill * until all mapped promises have fulfilled as well. If any promise in the array is rejected, or * any promise returned by the mapper function is rejected, the returned promise is rejected as well. * * The mapper function for a given item is called as soon as possible, that is, when the promise * for that item's index in the input array is fulfilled. This doesn't mean that the result array * has items in random order, it means that .map can be used for concurrency coordination unlike * .all. * * A common use of Promise.map is to replace the .push+Promise.all boilerplate: * * <pre> * var promises = []; * for (var i = 0; i < fileNames.length; ++i) { * promises.push(fs.readFileAsync(fileNames[i])); * } * qx.Promise.all(promises).then(function() { * console.log("done"); * }); * * // Using Promise.map: * qx.Promise.map(fileNames, function(fileName) { * // Promise.map awaits for returned promises as well. * return fs.readFileAsync(fileName); * }).then(function() { * console.log("done"); * }); * </pre> * * @param iterable {Iterable} An iterable object, such as an Array * @param iterator {Function} the callback, with <code>(value, index, length)</code> * @param options {Object?} options; can be: * <code>concurrency</code> max nuber of simultaneous filters, default is <code>Infinity</code> * @return {qx.Promise} */ map: function(iterable, iterator, options) { return qx.Promise.__callStaticMethod('map', arguments); }, /** * Given an <code>Iterable</code>(arrays are <code>Iterable</code>), or a promise of an * <code>Iterable</code>, which produces promises (or a mix of promises and values), iterate over * all the values in the <code>Iterable</code> into an array and iterate over the array serially, * in-order. * * Returns a promise for an array that contains the values returned by the iterator function in their * respective positions. The iterator won't be called for an item until its previous item, and the * promise returned by the iterator for that item are fulfilled. This results in a mapSeries kind of * utility but it can also be used simply as a side effect iterator similar to Array#forEach. * * If any promise in the input array is rejected or any promise returned by the iterator function is * rejected, the result will be rejected as well. * * Example where .mapSeries(the instance method) is used for iterating with side effects: * * <pre> * // Source: http://jakearchibald.com/2014/es7-async-functions/ * function loadStory() { * return getJSON('story.json') * .then(function(story) { * addHtmlToPage(story.heading); * return story.chapterURLs.map(getJSON); * }) * .mapSeries(function(chapter) { addHtmlToPage(chapter.html); }) * .then(function() { addTextToPage("All done"); }) * .catch(function(err) { addTextToPage("Argh, broken: " + err.message); }) * .then(function() { document.querySelector('.spinner').style.display = 'none'; }); * } * </pre> * * @param iterable {Iterable} An iterable object, such as an Array * @param iterator {Function} the callback, with <code>(value, index, length)</code> * @return {qx.Promise} */ mapSeries: function(iterable, iterator) { return qx.Promise.__callStaticMethod('mapSeries', arguments); }, /** * Given an <code>Iterable</code> (arrays are <code>Iterable</code>), or a promise of an * <code>Iterable</code>, which produces promises (or a mix of promises and values), iterate * over all the values in the <code>Iterable</code> into an array and reduce the array to a * value using the given reducer function. * * If the reducer function returns a promise, then the result of the promise is awaited, before * continuing with next iteration. If any promise in the array is rejected or a promise returned * by the reducer function is rejected, the result is rejected as well. * * Read given files sequentially while summing their contents as an integer. Each file contains * just the text 10. * * <pre> * qx.Promise.reduce(["file1.txt", "file2.txt", "file3.txt"], function(total, fileName) { * return fs.readFileAsync(fileName, "utf8").then(function(contents) { * return total + parseInt(contents, 10); * }); * }, 0).then(function(total) { * //Total is 30 * }); * </pre> * * If initialValue is undefined (or a promise that resolves to undefined) and the iterable contains * only 1 item, the callback will not be called and the iterable's single item is returned. If the * iterable is empty, the callback will not be called and initialValue is returned (which may be * undefined). * * Promise.reduce will start calling the reducer as soon as possible, this is why you might want to * use it over Promise.all (which awaits for the entire array before you can call Array#reduce on it). * * @param iterable {Iterable} An iterable object, such as an Array * @param reducer {Function} the callback, with <code>(value, index, length)</code> * @param initialValue {Object?} optional initial value * @return {qx.Promise} */ reduce: function(iterable, reducer, initialValue) { return qx.Promise.__callStaticMethod('reduce', arguments); }, /** * Returns a new function that wraps the given function fn. The new function will always return a promise that is * fulfilled with the original functions return values or rejected with thrown exceptions from the original function. * @param cb {Function} * @return {Function} */ method: function(cb) { var wrappedCb = qx.Promise.Bluebird.method(cb); return function() { return qx.Promise.__wrap(wrappedCb.apply(this, arguments)); }; }, /** * Like .all but for object properties or Maps* entries instead of iterated values. Returns a promise that * is fulfilled when all the properties of the object or the Map's' values** are fulfilled. The promise's * fulfillment value is an object or a Map with fulfillment values at respective keys to the original object * or a Map. If any promise in the object or Map rejects, the returned promise is rejected with the rejection * reason. * * If object is a trusted Promise, then it will be treated as a promise for object rather than for its * properties. All other objects (except Maps) are treated for their properties as is returned by * Object.keys - the object's own enumerable properties. * * @param input {Object} An Object * @return {qx.Promise} */ props: function(input) { return qx.Promise.__callStaticMethod('props', arguments); }, /** * Returns a new function that wraps a function that is in node.js * style. The resulting function returns a promise instead of taking a * callback function as an argument. The promise is resolved or rejected * by the action of the callback function. The provided function must * accept a callback as its last argument, and that callback function must * expect its first argument to be an error if non-null. If the first * argument is null, the second argument (optional) will be the success * value. * * Example: * * Assume there is a member method in myApp.Application such as the * following: * <pre><code> * issueRpc : function(method, params, callback) * { * ... * } * </code></pre> * * where the signature of <code>callback</code> is: * <pre><code> * function callback(e, result) * </code></pre> * * The <code>issueRpc</code>method could be converted to be called using * promises instead of callbacks, as shown here: * <pre><code> * var app = qx.core.Init.getApplication(); * var rpc = qx.Promise.promisify(app.issueRpc, { context : app }); * rpc("ping", [ "hello world" ]) * .then( * function(pongValue) * { * // handle result * }) * .catch( * function(e) * { * throw e; * }); * </code></pre> * * @param f {Function} The node.js-style function to be promisified * * @param options {Map?} * The sole user option in this map is <code>context</code>, which may * be specified to arrange for the provided callback function to be * called in the specified context. * * @return {qx.Promise} */ promisify : function(f, options) { return qx.Promise.__callStaticMethod('promisify', arguments); }, /* ********************************************************************************* * * Internal API methods * */ /** * Called when the Bluebird Promise class is loaded * @param Promise {Class} the Promise class */ __attachBluebird: function(Promise) { qx.Promise.Bluebird = Promise; Promise.config({ warnings: qx.core.Environment.get("qx.promise.warnings"), longStackTraces: qx.core.Environment.get("qx.promise.longStackTraces"), cancellation: true }); }, /** Whether one-time initialisaton has happened */ __initialized: false, /** * One-time initializer */ __initialize: function() { if (qx.Promise.__initialized) { return; } qx.Promise.__initialized = true; qx.bom.Event.addNativeListener(window, "unhandledrejection", qx.Promise.__onUnhandledRejection.bind(this)); if (!qx.core.Environment.get("qx.promise")) { qx.log.Logger.error(this, "Promises are installed and initialised but disabled from properties because qx.promise==false; this may cause unexpected behaviour"); } }, /** * Handles unhandled errors and passes them through to Qooxdoo's global error handler * @param e {NativeEvent} */ __onUnhandledRejection: function(e) { e.preventDefault(); var reason = null; if (e instanceof Error) { reason = e; } else if (e.reason instanceof Error) { reason = e.reason; } else if (e.detail && e.detail.reason instanceof Error) { reason = e.detail.reason; } qx.log.Logger.error(this, "Unhandled promise rejection: " + (reason ? reason.stack : "(not from exception)")); qx.event.GlobalError.handleError(reason); }, /** * Wraps values, converting Promise into qx.Promise * @param value {Object} * @return {Object} */ __wrap: function(value) { if (value instanceof qx.Promise.Bluebird) { if (value.$$qxPromise) { value = value.$$qxPromise; } else { value = new qx.Promise(value); } } return value; }, /** * Binds all functions in the array to the context at the end of the array; * the last value must be a qx.core.Object to distinguish itself from configuration * objects passed to some methods. * @param args {arguments} * @param minArgs {Integer?} minimum number of arguments expected for the method call; * this is used to determine whether the last value is for binding (default is 1) * @return {Array} array of new arguments with functions bound as necessary */ __bindArgs: function(args, minArgs) { args = qx.lang.Array.fromArguments(args); if (minArgs === undefined) { minArgs = 1; } if (args.length > minArgs) { var context = args[args.length - 1]; if (context instanceof qx.core.Object || qx.Class.isClass(context)) { args.pop(); for (var i = 0; i < args.length; i++) { if (typeof args[i] == "function") { args[i] = args[i].bind(context); } } } } return args; }, /** * Helper method used to call a Bluebird Promise method * @param methodName {String} method name to call * @param args {Array} arguments to pass * @param minArgs {Integer?} {@see __bindArgs} * @return {Object?} */ __callStaticMethod: function(methodName, args, minArgs) { args = qx.Promise.__bindArgs(args, minArgs); return qx.Promise.__wrap(qx.Promise.Bluebird[methodName].apply(qx.Promise.Bluebird, qx.Promise.__mapArgs(args))); }, /** * Maps all arguments ready for passing to a Bluebird function; qx.data.Array are * translated to native arrays and qx.Promise to Promise. This is not recursive. */ __mapArgs: function(args) { var dest = []; args.forEach(function(arg) { if (arg instanceof qx.data.Array) { dest.push(arg.toArray()); } else if (arg instanceof qx.Promise) { dest.push(arg.toPromise()); } else { dest.push(arg); } }); return dest; } }, defer: function(statics, members) { statics.Promise = statics.Native = window.Promise; var debug = qx.core.Environment.get("qx.debug"); qx.core.Environment.add("qx.promise.warnings", debug); qx.core.Environment.add("qx.promise.longStackTraces", false); } }); /** * @lint ignoreUnused(exports) * @lint ignoreUnused(module) * @lint ignoreUnused(define) * @lint ignoreUnused(CapturedTrace) * @lint ignoreUnused(bitField) * @lint ignoreUnused(isArray) * @lint ignoreDeprecated(eval) * @lint ignoreNoLoopBlock() * @ignore(Map) * @ignore(MutationObserver) * @ignore(Symbol) * @ignore(Symbol.iterator) * @ignore(_dereq_) * @ignore(chrome) * @ignore(chrome.loadTimes) * @ignore(define) * @ignore(define.amd) * @ignore(enumeration) * @ignore(exports) * @ignore(global) * @ignore(global.setImmediate) * @ignore(module) * @ignore(module.exports) * @ignore(obj) * @ignore(obj.toPromise) * @ignore(predicateLoop) * @ignore(process) * @ignore(process.domain) * @ignore(process.emit.apply) * @ignore(process.env) * @ignore(process.exit) * @ignore(process.nextTick) * @ignore(process.stderr.isTTY) * @ignore(process.stderr.write) * @ignore(process.versions.node.split) * @ignore(promise) * @ignore(Promise) * @ignore(setImmediate) */ (function() { /* @preserve * The MIT License (MIT) * * Copyright (c) 2013-2015 Petka Antonov * * Permission is hereby granted, free of charge, to any person obtaining a copy * of this software and associated documentation files (the "Software"), to deal * in the Software without restriction, including without limitation the rights * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell * copies of the Software, and to permit persons to whom the Software is * furnished to do so, subject to the following conditions: * * The above copyright notice and this permission notice shall be included in * all copies or substantial portions of the Software. * * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN * THE SOFTWARE. * */ /** * bluebird build version 3.4.5 * Features enabled: core, race, call_get, generators, map, nodeify, promisify, props, reduce, settle, some, using, timers, filter, any, each */ !function(e){ qx.Promise.__attachBluebird(e()); } (function(){ var define,module,exports; return (function e(t,n,r){ function s(o,u){ if(!n[o]){ if(!t[o]){ var a=typeof _dereq_=="function"&&_dereq_; if(!u&&a) return a(o,!0); if(i) return i(o,!0); var f=new Error("Cannot find module '"+o+"'"); throw f.code="MODULE_NOT_FOUND",f } var l=n[o]={exports:{}}; t[o][0].call(l.exports, function(e){ var n=t[o][1][e]; return s(n?n:e) },l,l.exports,e,t,n,r) } return n[o].exports } var i=typeof _dereq_=="function"&&_dereq_; for(var o=0;o<r.length;o++) s(r[o]); return s })({ 1:[ function(_dereq_,module,exports){ "use strict"; module.exports = function(Promise) { var SomePromiseArray = Promise._SomePromiseArray; function any(promises) { var ret = new SomePromiseArray(promises); var promise = ret.promise(); ret.setHowMany(1); ret.setUnwrap(); ret.init(); return promise; } Promise.any = function (promises) { return any(promises); }; Promise.prototype.any = function () { return any(this); }; }; },{}],2:[function(_dereq_,module,exports){ "use strict"; var firstLineError; try {throw new Error(); } catch (e) {firstLineError = e;} var schedule = _dereq_("./schedule"); var Queue = _dereq_("./queue"); var util = _dereq_("./util"); function Async() { this._customScheduler = false; this._isTickUsed = false; this._lateQueue = new Queue(16); this._normalQueue = new Queue(16); this._haveDrainedQueues = false; this._trampolineEnabled = true; var self = this; this.drainQueues = function () { self._drainQueues(); }; this._schedule = schedule; } Async.prototype.setScheduler = function(fn) { var prev = this._schedule; this._schedule = fn; this._customScheduler = true; return prev; }; Async.prototype.hasCustomScheduler = function() { return this._customScheduler; }; Async.prototype.enableTrampoline = function() { this._trampolineEnabled = true; }; Async.prototype.disableTrampolineIfNecessary = function() { if (util.hasDevTools) { this._trampolineEnabled = false; } }; Async.prototype.haveItemsQueued = function () { return this._isTickUsed || this._haveDrainedQueues; }; Async.prototype.fatalError = function(e, isNode) { if (isNode) { process.stderr.write("Fatal " + (e instanceof Error ? e.stack : e) + "\n"); process.exit(2); } else { this.throwLater(e); } }; Async.prototype.throwLater = function(fn, arg) { if (arguments.length === 1) { arg = fn; fn = function () { throw arg; }; } if (typeof setTimeout !== "undefined") { setTimeout(function() { fn(arg); }, 0); } else try { this._schedule(function() { fn(arg); }); } catch (e) { throw new Error("No async scheduler available\u000a\u000a See http://goo.gl/MqrFmX\u000a"); } }; function AsyncInvokeLater(fn, receiver, arg) { this._lateQueue.push(fn, receiver, arg); this._queueTick(); } function AsyncInvoke(fn, receiver, arg) { this._normalQueue.push(fn, receiver, arg); this._queueTick(); } function AsyncSettlePromises(promise) { this._normalQueue._pushOne(promise); this._queueTick(); } if (!util.hasDevTools) { Async.prototype.invokeLater = AsyncInvokeLater; Async.prototype.invoke = AsyncInvoke; Async.prototype.settlePromises = AsyncSettlePromises; } else { Async.prototype.invokeLater = function (fn, receiver, arg) { if (this._trampolineEnabled) { AsyncInvokeLater.call(this, fn, receiver, arg); } else { this._schedule(function() { setTimeout(function() { fn.call(receiver, arg); }, 100); }); } }; Async.prototype.invoke = function (fn, receiver, arg) { if (this._trampolineEnabled) { AsyncInvoke.call(this, fn, receiver, arg); } else { this._schedule(function() { fn.call(receiver, arg); }); } }; Async.prototype.settlePromises = function(promise) { if (this._trampolineEnabled) { AsyncSettlePromises.call(this, promise); } else { this._schedule(function() { promise._settlePromises(); }); } }; } Async.prototype.invokeFirst = function (fn, receiver, arg) { this._normalQueue.unshift(fn, receiver, arg); this._queueTick(); }; Async.prototype._drainQueue = function(queue) { while (queue.length() > 0) { var fn = queue.shift(); if (typeof fn !== "function") { fn._settlePromises(); continue; } var receiver = queue.shift(); var arg = queue.shift(); fn.call(receiver, arg); } }; Async.prototype._drainQueues = function () { this._drainQueue(this._normalQueue); this._reset(); this._haveDrainedQueues = true; this._drainQueue(this._lateQueue); }; Async.prototype._queueTick = function () { if (!this._isTickUsed) { this._isTickUsed = true; this._schedule(this.drainQueues); } }; Async.prototype._reset = function () { this._isTickUsed = false; }; module.exports = Async; module.exports.firstLineError = firstLineError; },{"./queue":26,"./schedule":29,"./util":36}],3:[function(_dereq_,module,exports){ "use strict"; module.exports = function(Promise, INTERNAL, tryConvertToPromise, debug) { var calledBind = false; var rejectThis = function(_, e) { this._reject(e); }; var targetRejected = function(e, context) { context.promiseRejectionQueued = true; context.bindingPromise._then(rejectThis, rejectThis, null, this, e); }; var bindingResolved = function(thisArg, context) { if (((this._bitField & 50397184) === 0)) { this._resolveCallback(context.target); } }; var bindingRejected = function(e, context) { if (!context.promiseRejectionQueued) this._reject(e); }; Promise.prototype.bind = function (thisArg) { if (!calledBind) { calledBind = true; Promise.prototype._propagateFrom = debug.propagateFromFunction(); Promise.prototype._boundValue = debug.boundValueFunction(); } var maybePromise