UNPKG

ember-concurrency

Version:

Improved concurrency/async primitives for Ember.js

136 lines (129 loc) 5.44 kB
import { assert } from '@ember/debug'; import RSVP, { Promise as Promise$1 } from 'rsvp'; import { TaskInstance } from './task-instance.js'; import { cancelableSymbol, Yieldable } from './external/yieldables.js'; /** * A cancelation-aware variant of [Promise.all](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Promise/all). * The normal version of a `Promise.all` just returns a regular, uncancelable * Promise. The `ember-concurrency` variant of `all()` has the following * additional behavior: * * - if the task that `yield`ed `all()` is canceled, any of the * {@linkcode TaskInstance}s passed in to `all` will be canceled * - if any of the {@linkcode TaskInstance}s (or regular promises) passed in reject (or * are canceled), all of the other unfinished `TaskInstance`s will * be automatically canceled. * * [Check out the "Awaiting Multiple Child Tasks example"](/docs/examples/joining-tasks) */ const all = taskAwareVariantOf(RSVP.Promise, 'all', identity); /** * A cancelation-aware variant of [RSVP.allSettled](https://api.emberjs.com/ember/release/functions/rsvp/allSettled). * The normal version of a `RSVP.allSettled` just returns a regular, uncancelable * Promise. The `ember-concurrency` variant of `allSettled()` has the following * additional behavior: * * - if the task that `yield`ed `allSettled()` is canceled, any of the * {@linkcode TaskInstance}s passed in to `allSettled` will be canceled */ const allSettled = taskAwareVariantOf(RSVP, 'allSettled', identity); /** * A cancelation-aware variant of [Promise.race](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Promise/race). * The normal version of a `Promise.race` just returns a regular, uncancelable * Promise. The `ember-concurrency` variant of `race()` has the following * additional behavior: * * - if the task that `yield`ed `race()` is canceled, any of the * {@linkcode TaskInstance}s passed in to `race` will be canceled * - once any of the tasks/promises passed in complete (either success, failure, * or cancelation), any of the {@linkcode TaskInstance}s passed in will be canceled * * [Check out the "Awaiting Multiple Child Tasks example"](/docs/examples/joining-tasks) */ const race = taskAwareVariantOf(Promise$1, 'race', identity); /** * A cancelation-aware variant of [RSVP.hash](https://api.emberjs.com/ember/release/functions/rsvp/hash). * The normal version of a `RSVP.hash` just returns a regular, uncancelable * Promise. The `ember-concurrency` variant of `hash()` has the following * additional behavior: * * - if the task that `yield`ed `hash()` is canceled, any of the * {@linkcode TaskInstance}s passed in to `hash` will be canceled * - if any of the items rejects/cancels, all other cancelable items * (e.g. {@linkcode TaskInstance}s) will be canceled */ const hash = taskAwareVariantOf(RSVP, 'hash', getValues); /** * A cancelation-aware variant of [RSVP.hashSettled](https://api.emberjs.com/ember/release/functions/rsvp/hashSettled). * The normal version of a `RSVP.hashSettled` just returns a regular, uncancelable * Promise. The `ember-concurrency` variant of `hashSettled()` has the following * additional behavior: * * - if the task that `yield`ed `hashSettled()` is canceled, any of the * {@linkcode TaskInstance}s passed in to `hashSettled` will be canceled */ const hashSettled = taskAwareVariantOf(RSVP, 'hashSettled', getValues); function identity(obj) { return obj; } function getValues(obj) { return Object.keys(obj).map(k => obj[k]); } function castForPromiseHelper(castable) { if (castable) { if (castable instanceof TaskInstance) { // Mark TaskInstances, including those that performed synchronously and // have finished already, as having their errors handled, as if they had // been then'd, which this is emulating. castable.executor.asyncErrorsHandled = true; } else if (castable instanceof Yieldable) { // Cast to promise return castable._toPromise(); } } return castable; } function castAwaitables(arrOrHash, callback) { if (Array.isArray(arrOrHash)) { return arrOrHash.map(callback); } else if (typeof arrOrHash === 'object' && arrOrHash !== null) { let obj = {}; Object.keys(arrOrHash).forEach(key => { obj[key] = callback(arrOrHash[key]); }); return obj; } else { // :shruggie: return arrOrHash; } } function taskAwareVariantOf(obj, method, getItems) { return function (awaitable) { let awaitables = castAwaitables(awaitable, castForPromiseHelper); let items = getItems(awaitables); assert(`'${method}' expects an array.`, Array.isArray(items)); let defer = RSVP.defer(); obj[method](awaitables).then(defer.resolve, defer.reject); let hasCancelled = false; let cancelAll = () => { if (hasCancelled) { return; } hasCancelled = true; items.forEach(it => { if (it) { if (it instanceof TaskInstance) { it.cancel(); } else if (typeof it[cancelableSymbol] === 'function') { it[cancelableSymbol](); } } }); }; let promise = defer.promise.finally(cancelAll); promise[cancelableSymbol] = cancelAll; return promise; }; } export { all, allSettled, hash, hashSettled, race }; //# sourceMappingURL=cancelable-promise-helpers.js.map