UNPKG

@qooxdoo/framework

Version:

The JS Framework for Coders

1,090 lines (1,014 loc) 41.3 kB
/* ************************************************************************ qooxdoo - the new era of web development http://qooxdoo.org Copyright: 2016 Zenesis Limited, http://www.zenesis.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 * 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.*) * */ 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. * * @overload * @param fn {Function} the promise function called with <code>(resolve, reject)</code> * @param context {Object?} optional context for all callbacks * * @overload * @param promise {qx.promise.NativeWrapper | qx.promise.BluebirdImpl.Bluebird} a promise to wrap */ construct(fn, context) { super(); qx.Promise.__initialize(); if (fn instanceof qx.Promise.Impl) { 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.Impl(fn); } else { this.__p = new qx.Promise.Impl(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, /** * Whether the depracation warning of qx.Promise#cancel has been shown */ __cancelWarningShown: false, /* ********************************************************************************* * * 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. * * @deprecated * While Bluebird supports this feature, * this is not ratified by ES6 and there are no plans to add this to the native promise any time soon. */ cancel() { if (qx.core.Environment.get("qx.debug")) { if (qx.core.Environment.get("qx.Promise.useNativePromise")) { throw new Error( "qx.Promise.cancel is not supported when the environment key 'qx.Promise.useNativePromise' is true" ); } else if (!qx.Promise.__cancelWarningShown) { qx.Promise.__cancelWarningShown = true; console.warn( "qx.Promise.cancel is deprecated and will be removed in the future" ); } } 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 iterator {Function} the callback, with <code>(value, index, length)</code> * @return {qx.Promise} */ forEach(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 iterator {Function} the callback, with <code>(value, index, length)</code> * @param options {Object?} Either: * A native object with one key: <code>concurrency</code>: max number of simultaneous filters, default is <code>Infinity</code> * Or: any other object (subclass of qx.core.Object), in which case this will be the context for the iterator * @return {qx.Promise} */ filter(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 iterator {Function} the callback, with <code>(value, index, length)</code> * @param options {Object?} Either: * A native object with one key: <code>concurrency</code>: max number of simultaneous filters, default is <code>Infinity</code> * Or: any other object (subclass of qx.core.Object), in which case this will be the context for the iterator * @return {qx.Promise} */ map(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 iterator {Function} the callback, with <code>(value, index, length)</code> * @return {qx.Promise} */ mapSeries(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 reducer {Function} the callback, with <code>(value, index, length)</code> * @param initialValue {Object?} optional initial value * @return {qx.Promise} */ reduce(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.Impl.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. * If the environment key `qx.Promise.useNativePromise` is set to true, * it will be qx.promise.NativeWrapper, otherwise it will be qx.promise.BluebirdImpl.Bluebird. * * The underlying implementation 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: { /** * Internal implementation of the promise. Either Bluebird or the native Promise wrapper * (qx.promise.NativeWrapper) depending on the environment setting `qx.Promise.useNativePromise`. */ Impl: 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.Impl.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.Impl.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) { return function (...args) { return new qx.Promise((resolve, reject) => { function callback(err, result) { if (err) { reject(err); } else { resolve(result); } } args.push(callback); f.call(this, ...args); }); }; }, /* ********************************************************************************* * * Internal API methods * */ /** 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.Impl) { 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 Promise method in the underlying implementation * @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.Impl[methodName].apply( qx.Promise.Impl, 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; } }, environment: { /** * Whether to use the native Promise as the underlying implementation for qx.Promise * instead of Bluebird. */ "qx.Promise.useNativePromise": false, "qx.promise.longStackTraces": false }, defer(statics, members) { var debug = qx.core.Environment.get("qx.debug"); qx.core.Environment.add("qx.promise.warnings", debug); let Impl; if (qx.core.Environment.get("qx.Promise.useNativePromise")) { Impl = qx.promise.NativeWrapper; } else { Impl = qx.promise.BluebirdImpl.Bluebird; } statics.Impl = Impl; } });