UNPKG

@qooxdoo/framework

Version:

The JS Framework for Coders

1,351 lines (1,255 loc) 291 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.*) */ /* global global, setImmediate, chrome, _dereq_ */ /* eslint-disable no-global-assign */ 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(fn, context) { super(); 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() { 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(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(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(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(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(onRejected) { return this._callMethod("finally", arguments); }, /** * Cancel this promise. Will not do anything if this promise is already settled. */ cancel() { 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() { 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(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(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(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(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(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(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(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(iterable, reducer, initialValue) { return this._callIterableMethod("reduce", arguments); }, /** * External promise handler */ __externalPromise(resolve, reject) { this.__external = { resolve: resolve, reject: reject, complete: false }; }, /** * Returns the data stored by __externalPromise, throws an exception once processed */ __getPendingExternal() { 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(value) { this.__getPendingExternal().resolve(value); }, /** * Rejects an external promise */ reject(reason) { this.__getPendingExternal().reject(reason); }, /* ********************************************************************************* * * Utility methods * */ /** * Helper method used to call Promise methods which iterate over an array */ _callIterableMethod(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(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() { 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 * */ /** * Detects whether the value is a promise. * * Note that this is not an `instanceof` check and while it may look odd to just test whether * there is a property called `then` which is a Function, that's the actual spec - * @see https://promisesaplus.com/ * * The difficulty is that it also needs to have a `.finally` and `.catch` methods in order to * always be routinely useful; it's debatable what we can do about that here - if the calling code * definitely requires a promise then it can use `.resolve` to upgrade it or make sure that it is * a fully featured promise. In this function, we detect that it is thenable, and then give a warning * if it is not catchable. * * @param {*} value * @returns {Boolean} true if it is a promise */ isPromise(value) { if (!value || typeof value.then != "function") return false; if (qx.core.Environment.get("qx.debug")) { if ( typeof value.finally != "function" || typeof value.catch != "function" ) { qx.log.Logger.warn( qx.Promise, 'Calling `isPromise` on a "thenable" instance but the object does not also support `.catch` and/or `.finally`' ); } } return true; }, /** * 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(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(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(value) { function action(value) { var arr = []; var names = []; for (var name in value) { if (value.hasOwnProperty(name) && qx.Promise.isPromise(value[name])) { 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 qx.Promise.isPromise(value) ? 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(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(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(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(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(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(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(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(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(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(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(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(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(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() { if (qx.Promise.__initialized) { return; } qx.Promise.__initialized = true; var isNode = typeof process !== "undefined"; if (isNode) { process.on( "unhandledRejection", qx.Promise.__onUnhandledRejection.bind(this) ); } else { 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(e) { if (qx.lang.Type.isFunction(e.preventDefault)) { 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(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(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(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(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(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); } }); /*eslint curly: ["off"]*/ /** * @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(CustomEvent) * @ignore(define) * @ignore(define.amd) * @ignore(enumeration) * @ignore(Event) * @ignore(exports) * @ignore(global) * @ignore(global.setImmediate) * @ignore(module) * @ignore(module.exports) * @ignore(MutationObserver) * @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(self) * @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 + "'"); /* eslint-disable-next-line no-sequences */ 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") {