From 05cbb1a229dac91c61905fa08b48d84829697bd9 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jan=20Buscht=C3=B6ns?= Date: Tue, 6 Aug 2019 21:25:45 +0200 Subject: [PATCH 1/7] refactor: PoC for TypeScript type support --- addon/ember-concurrency.d.ts | 139 ++++++++++++++++++++++++++++++++++ addon/index.ts | 46 +++++++++-- tests/unit/decorators-test.ts | 83 +++++++++++++------- tests/unit/last-value-test.ts | 10 +-- types/ember-concurrency.d.ts | 25 ------ 5 files changed, 237 insertions(+), 66 deletions(-) create mode 100644 addon/ember-concurrency.d.ts delete mode 100644 types/ember-concurrency.d.ts diff --git a/addon/ember-concurrency.d.ts b/addon/ember-concurrency.d.ts new file mode 100644 index 0000000..aff3cd4 --- /dev/null +++ b/addon/ember-concurrency.d.ts @@ -0,0 +1,139 @@ +declare module 'ember-concurrency' { + import EmberObject from '@ember/object'; + import { + UnwrapComputedPropertyGetter, + UnwrapComputedPropertyGetters + } from '@ember/object/-private/types'; + + import RSVP from 'rsvp'; + + // Lifted from @types/ember__object/observable.d.ts + interface Getter { + /** + * Retrieves the value of a property from the object. + */ + get(key: K): UnwrapComputedPropertyGetter; + /** + * To get the values of multiple properties at once, call `getProperties` + * with a list of strings or an array: + */ + getProperties( + list: K[] + ): Pick, K>; + getProperties( + ...list: K[] + ): Pick, K>; + } + + export type GeneratorFn = ( + ...args: Args + ) => IterableIterator; + + export const all: typeof Promise.all; + export const allSettled: typeof RSVP.allSettled; + export const hash: typeof RSVP.hash; + export const race: typeof Promise.race; + + export function timeout(ms: number): Promise; + + export function waitForEvent( + object: EmberObject | EventTarget, + eventName: string + ): Promise; + + export function waitForProperty( + object: T, + key: K, + callbackOrValue: (value: T[K]) => boolean + ): Promise; + + export function waitForQueue(queueName: string): Promise; + + export function task( + taskFn: GeneratorFn + ): Task>>; + export function task(encapsulatedTask: { + perform: GeneratorFn; + }): Task>>; + + export function taskGroup(): TaskGroupProperty; + + interface CommonTaskProperty { + restartable: () => TaskProperty; + drop: () => TaskProperty; + keepLatest: () => TaskProperty; + enqueue: () => TaskProperty; + maxConcurrency: (n: number) => TaskProperty; + cancelOn: (eventName: string) => TaskProperty; + group: (groupName: string) => TaskProperty; + } + + export interface TaskProperty extends CommonTaskProperty { + evented: () => TaskProperty; + debug: () => TaskProperty; + on: (eventName: string) => TaskProperty; + } + + // eslint-disable-next-line @typescript-eslint/no-empty-interface + export interface TaskGroupProperty extends CommonTaskProperty {} + + // Based on https://github.com/CenterForOpenScience/ember-osf-web/blob/7933316efae805e00723789809bdeb58a96a286a/types/ember-concurrency/index.d.ts + + export enum TaskInstanceState { + Dropped = 'dropped', + Canceled = 'canceled', + Finished = 'finished', + Running = 'running', + Waiting = 'waiting' + } + + export interface TaskInstance extends PromiseLike, Getter { + readonly error?: unknown; + readonly hasStarted: boolean; + readonly isCanceled: boolean; + readonly isDropped: boolean; + readonly isError: boolean; + readonly isFinished: boolean; + readonly isRunning: boolean; + readonly isSuccessful: boolean; + readonly state: TaskInstanceState; + readonly value?: T; + cancel(): void; + catch(): RSVP.Promise; + finally(): RSVP.Promise; + then( + onfulfilled?: + | ((value: T) => TResult1 | RSVP.Promise) + | undefined + | null, + onrejected?: + | ((reason: any) => TResult2 | PromiseLike) + | undefined + | null + ): RSVP.Promise; + } + + export enum TaskState { + Running = 'running', + Queued = 'queued', + Idle = 'idle' + } + + export interface Task extends Getter { + readonly isIdle: boolean; + readonly isQueued: boolean; + readonly isRunning: boolean; + readonly last?: TaskInstance; + readonly lastCanceled?: TaskInstance; + readonly lastComplete?: TaskInstance; + readonly lastErrored?: TaskInstance; + readonly lastIncomplete?: TaskInstance; + readonly lastPerformed?: TaskInstance; + readonly lastRunning?: TaskInstance; + readonly lastSuccessful?: TaskInstance; + readonly performCount: number; + readonly state: TaskState; + perform(...args: Args): TaskInstance; + cancelAll(): void; + } +} diff --git a/addon/index.ts b/addon/index.ts index c08cd2f..07c8423 100644 --- a/addon/index.ts +++ b/addon/index.ts @@ -8,7 +8,9 @@ import { task as createTaskProperty, taskGroup as createTaskGroupProperty, TaskProperty, - TaskGroupProperty + TaskGroupProperty, + Task, + GeneratorFn } from 'ember-concurrency'; export { default as lastValue } from './last-value'; @@ -86,7 +88,7 @@ function createTaskFromDescriptor(desc: DecoratorDescriptor) { (typeof value === 'object' && typeof value.perform === 'function') ); - return createTaskProperty(value); + return (createTaskProperty(value) as unknown) as TaskProperty; } /** @@ -110,14 +112,15 @@ function createTaskGroupFromDescriptor(_desc: DecoratorDescriptor) { */ function applyOptions( options: TaskGroupOptions, - task: TaskGroupProperty + taskProperty: TaskGroupProperty ): TaskGroupProperty & Decorator; function applyOptions( options: TaskOptions, - task: TaskProperty + taskProperty: TaskProperty ): TaskProperty & Decorator { return Object.entries(options).reduce( ( + // eslint-disable-next-line no-shadow taskProperty, [key, value]: [ keyof typeof options, @@ -135,7 +138,7 @@ function applyOptions( value ); }, - task + taskProperty // The CP decorator gets executed in `createDecorator` ) as TaskProperty & Decorator; } @@ -192,7 +195,38 @@ const createDecorator = ( * @param {object?} [options={}] * @return {TaskProperty} */ -export const task = createDecorator(createTaskFromDescriptor); +const taskDecorator = createDecorator(createTaskFromDescriptor); + +export function task( + taskFn: GeneratorFn +): Task>>; +export function task(encapsulatedTask: { + perform: GeneratorFn; +}): Task>>; +export function task(options: TaskOptions): PropertyDecorator; +export function task( + target: Record, + propertyKey: string | symbol +): void; +export function task( + ...args: + | [GeneratorFn] + | [{ perform: GeneratorFn }] + | [TaskOptions] + | [Record, string | symbol] +): Task>> | PropertyDecorator | void { + const [firstParam] = args; + if ( + typeof firstParam === 'function' || + (typeof firstParam === 'object' && + // @ts-ignore + typeof firstParam.perform === 'function') + ) + // @ts-ignore + return firstParam; + // @ts-ignore + return taskDecorator(...args); +} /** * Turns the decorated generator function into a task and applies the diff --git a/tests/unit/decorators-test.ts b/tests/unit/decorators-test.ts index c5e16b3..01749f4 100644 --- a/tests/unit/decorators-test.ts +++ b/tests/unit/decorators-test.ts @@ -19,60 +19,80 @@ module('Unit | decorators', function() { class TestSubject extends EmberObject { @task - doStuff = function*() { + doStuff = task(function*() { yield; return 123; - }; + }); @restartableTask - a = function*() { + a = task(function*() { yield; return 456; - }; + }); @keepLatestTask - b = function*() { + b = task(function*() { yield; return 789; - }; + }); @dropTask - c = function*() { + c = task(function*() { yield; return 12; - }; + }); @enqueueTask - d = function*() { + d = task(function*() { yield; return 34; - }; + }); } let subject!: TestSubject; run(() => { subject = TestSubject.create(); - // @ts-ignore subject.get('doStuff').perform(); - // @ts-ignore subject.get('a').perform(); - // @ts-ignore subject.get('b').perform(); - // @ts-ignore subject.get('c').perform(); - // @ts-ignore subject.get('d').perform(); }); - // @ts-ignore - assert.equal(subject.get('doStuff.last.value'), 123); - // @ts-ignore - assert.equal(subject.get('a.last.value'), 456); - // @ts-ignore - assert.equal(subject.get('b.last.value'), 789); - // @ts-ignore - assert.equal(subject.get('c.last.value'), 12); - // @ts-ignore - assert.equal(subject.get('d.last.value'), 34); + assert.equal( + subject + .get('doStuff') + .get('last')! + .get('value'), + 123 + ); + assert.equal( + subject + .get('a') + .get('last')! + .get('value'), + 456 + ); + assert.equal( + subject + .get('b') + .get('last')! + .get('value'), + 789 + ); + assert.equal( + subject + .get('c') + .get('last')! + .get('value'), + 12 + ); + assert.equal( + subject + .get('d') + .get('last')! + .get('value'), + 34 + ); }); // This has actually never worked. @@ -81,13 +101,13 @@ module('Unit | decorators', function() { class TestSubject extends EmberObject { @task - encapsulated = { + encapsulated = task({ privateState: 56, *perform() { yield; return this.privateState; } - }; + }); } let subject!: TestSubject; @@ -95,7 +115,12 @@ module('Unit | decorators', function() { subject = TestSubject.create(); subject.get('encapsulated').perform(); }); - // @ts-ignore - assert.equal(subject.get('encapsulated.last.value'), 56); + assert.equal( + subject + .get('encapsulated') + .get('last') + .get('value'), + 56 + ); }); }); diff --git a/tests/unit/last-value-test.ts b/tests/unit/last-value-test.ts index 3ae6a9f..c39e59c 100644 --- a/tests/unit/last-value-test.ts +++ b/tests/unit/last-value-test.ts @@ -14,9 +14,9 @@ module('Unit | last-value', function(hooks) { test('without a default value', function(assert) { class ObjectWithTask extends EmberObject { @task - task = function*() { + task = task(function*() { return yield 'foo'; - }; + }); @lastValue('task') value?: 'foo'; @@ -29,7 +29,6 @@ module('Unit | last-value', function(hooks) { 'it returns nothing if the task has not been performed' ); - // @ts-ignore instance.get('task').perform(); nextLoop(); @@ -43,9 +42,9 @@ module('Unit | last-value', function(hooks) { test('with a default value', function(assert) { class ObjectWithTaskDefaultValue extends EmberObject { @task - task = function*() { + task = task(function*() { return yield 'foo'; - }; + }); @lastValue('task') value = 'default value'; @@ -59,7 +58,6 @@ module('Unit | last-value', function(hooks) { 'it returns the default value if the task has not been performed' ); - // @ts-ignore instance.get('task').perform(); nextLoop(); diff --git a/types/ember-concurrency.d.ts b/types/ember-concurrency.d.ts deleted file mode 100644 index 88dad0a..0000000 --- a/types/ember-concurrency.d.ts +++ /dev/null @@ -1,25 +0,0 @@ -export function task(fn: () => IterableIterator): TaskProperty; - -export function taskGroup(): TaskGroupProperty; - -interface CommonTaskProperty { - restartable: () => TaskProperty; - drop: () => TaskProperty; - keepLatest: () => TaskProperty; - enqueue: () => TaskProperty; - maxConcurrency: (n: number) => TaskProperty; - cancelOn: (eventName: string) => TaskProperty; - group: (groupName: string) => TaskProperty; -} - -export interface TaskProperty extends CommonTaskProperty { - evented: () => TaskProperty; - debug: () => TaskProperty; - on: (eventName: string) => TaskProperty; -} - -// eslint-disable-next-line @typescript-eslint/no-empty-interface -export interface TaskGroupProperty extends CommonTaskProperty {} - -// eslint-disable-next-line @typescript-eslint/no-empty-interface -export interface TaskInstance {} From 2ce5abb92c7b27e45e4a7b1323c385ad3007f8bb Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jan=20Buscht=C3=B6ns?= Date: Tue, 6 Aug 2019 22:09:23 +0200 Subject: [PATCH 2/7] test: add task with parameters --- tests/unit/decorators-test.ts | 14 ++++++++++++++ 1 file changed, 14 insertions(+) diff --git a/tests/unit/decorators-test.ts b/tests/unit/decorators-test.ts index 01749f4..0c7e0ef 100644 --- a/tests/unit/decorators-test.ts +++ b/tests/unit/decorators-test.ts @@ -24,6 +24,12 @@ module('Unit | decorators', function() { return 123; }); + @task + withParameters = task(function*(foo: string, bar: boolean) { + yield; + return { foo, bar }; + }); + @restartableTask a = task(function*() { yield; @@ -53,6 +59,7 @@ module('Unit | decorators', function() { run(() => { subject = TestSubject.create(); subject.get('doStuff').perform(); + subject.get('withParameters').perform('abc', true); subject.get('a').perform(); subject.get('b').perform(); subject.get('c').perform(); @@ -65,6 +72,13 @@ module('Unit | decorators', function() { .get('value'), 123 ); + assert.deepEqual( + subject + .get('withParameters') + .get('last')! + .get('value'), + { foo: 'abc', bar: true } + ); assert.equal( subject .get('a') From 66830d7b5f8b4e23d51c61c5d7658620d700b72f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jan=20Buscht=C3=B6ns?= Date: Tue, 6 Aug 2019 22:24:08 +0200 Subject: [PATCH 3/7] test: even more param / ctx --- tests/unit/decorators-test.ts | 14 ++++++++------ 1 file changed, 8 insertions(+), 6 deletions(-) diff --git a/tests/unit/decorators-test.ts b/tests/unit/decorators-test.ts index 0c7e0ef..daf8e84 100644 --- a/tests/unit/decorators-test.ts +++ b/tests/unit/decorators-test.ts @@ -18,10 +18,12 @@ module('Unit | decorators', function() { assert.expect(5); class TestSubject extends EmberObject { + readonly someProperty = 123; + @task - doStuff = task(function*() { + doStuff = task(function*(this: TestSubject) { yield; - return 123; + return this.someProperty; }); @task @@ -117,9 +119,9 @@ module('Unit | decorators', function() { @task encapsulated = task({ privateState: 56, - *perform() { + *perform(_foo: string) { yield; - return this.privateState; + return this.privateState; // @TODO: broken } }); } @@ -127,12 +129,12 @@ module('Unit | decorators', function() { let subject!: TestSubject; run(() => { subject = TestSubject.create(); - subject.get('encapsulated').perform(); + subject.get('encapsulated').perform('abc'); }); assert.equal( subject .get('encapsulated') - .get('last') + .get('last')! .get('value'), 56 ); From 5abc1d5227aeb429269109b84582644c6e3f08df Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jan=20Buscht=C3=B6ns?= Date: Tue, 6 Aug 2019 22:24:51 +0200 Subject: [PATCH 4/7] test: [revert me] attempt at encapsulated tasks --- addon/ember-concurrency.d.ts | 7 ++++--- addon/index.ts | 17 +++++++++++------ 2 files changed, 15 insertions(+), 9 deletions(-) diff --git a/addon/ember-concurrency.d.ts b/addon/ember-concurrency.d.ts index aff3cd4..926bda8 100644 --- a/addon/ember-concurrency.d.ts +++ b/addon/ember-concurrency.d.ts @@ -25,7 +25,8 @@ declare module 'ember-concurrency' { ): Pick, K>; } - export type GeneratorFn = ( + export type GeneratorFn = ( + this: T, ...args: Args ) => IterableIterator; @@ -50,10 +51,10 @@ declare module 'ember-concurrency' { export function waitForQueue(queueName: string): Promise; export function task( - taskFn: GeneratorFn + taskFn: GeneratorFn ): Task>>; export function task(encapsulatedTask: { - perform: GeneratorFn; + perform: GeneratorFn; }): Task>>; export function taskGroup(): TaskGroupProperty; diff --git a/addon/index.ts b/addon/index.ts index 07c8423..33f203f 100644 --- a/addon/index.ts +++ b/addon/index.ts @@ -198,11 +198,16 @@ const createDecorator = ( const taskDecorator = createDecorator(createTaskFromDescriptor); export function task( - taskFn: GeneratorFn + taskFn: GeneratorFn ): Task>>; -export function task(encapsulatedTask: { - perform: GeneratorFn; -}): Task>>; +export function task< + Args extends any[], + R, + E extends { + // @TODO: this does not work + perform: GeneratorFn; + } +>(encapsulatedTask: E): Task>>; export function task(options: TaskOptions): PropertyDecorator; export function task( target: Record, @@ -210,8 +215,8 @@ export function task( ): void; export function task( ...args: - | [GeneratorFn] - | [{ perform: GeneratorFn }] + | [GeneratorFn] + | [{ perform: GeneratorFn }] | [TaskOptions] | [Record, string | symbol] ): Task>> | PropertyDecorator | void { From 05fe952fa08add9a5b73c3fd105b67749be38242 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jan=20Buscht=C3=B6ns?= Date: Wed, 7 Aug 2019 16:43:48 +0200 Subject: [PATCH 5/7] chore: make CI and tests pass --- tests/unit/decorators-test.ts | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/tests/unit/decorators-test.ts b/tests/unit/decorators-test.ts index daf8e84..1b90b08 100644 --- a/tests/unit/decorators-test.ts +++ b/tests/unit/decorators-test.ts @@ -15,7 +15,7 @@ import { module('Unit | decorators', function() { test('Basic decorators functionality', function(assert) { - assert.expect(5); + assert.expect(6); class TestSubject extends EmberObject { readonly someProperty = 123; @@ -121,6 +121,7 @@ module('Unit | decorators', function() { privateState: 56, *perform(_foo: string) { yield; + // @ts-ignore return this.privateState; // @TODO: broken } }); From 8ec57cba38eb147862a00af6d6a44a8f20e2b743 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jan=20Buscht=C3=B6ns?= Date: Wed, 7 Aug 2019 17:32:24 +0200 Subject: [PATCH 6/7] chore: improve types for `ember-concurrency` --- addon/ember-concurrency.d.ts | 310 ++++++++++++++++++++++++++++++++++- 1 file changed, 305 insertions(+), 5 deletions(-) diff --git a/addon/ember-concurrency.d.ts b/addon/ember-concurrency.d.ts index 926bda8..0bde98f 100644 --- a/addon/ember-concurrency.d.ts +++ b/addon/ember-concurrency.d.ts @@ -37,17 +37,44 @@ declare module 'ember-concurrency' { export function timeout(ms: number): Promise; + /** + * Use `waitForEvent` to pause the task until an event is fired. + * The event can either be a jQuery event or an `Ember.Evented` event, or any + * event system where the object supports `.on()`, `.one()` and `.off()`. + */ export function waitForEvent( object: EmberObject | EventTarget, eventName: string ): Promise; + /** + * Use `waitForProperty` to pause the task until a property on an object + * changes to some expected value. + * + * This can be used for a variety of use cases, including synchronizing with + * another task by waiting for it to become idle, or change state in some + * other way. + * + * If you omit the callback, `waitForProperty` will resume execution when the + * observed property becomes truthy. If you provide a callback, it'll be + * called immediately with the observed property's current value, and multiple + * times thereafter whenever the property changes, until you return a truthy + * value from the callback, or the current task is canceled. + * + * You can also pass in a non-`function` value in place of the callback, in + * which case the task will continue executing when the property's value + * becomes the value that you passed in. + */ export function waitForProperty( object: T, key: K, - callbackOrValue: (value: T[K]) => boolean + callbackOrValue?: (value: T[K]) => boolean | any ): Promise; + /** + * Use `waitForQueue` to pause the task until a certain run loop queue is + * reached. + */ export function waitForQueue(queueName: string): Promise; export function task( @@ -80,26 +107,110 @@ declare module 'ember-concurrency' { // Based on https://github.com/CenterForOpenScience/ember-osf-web/blob/7933316efae805e00723789809bdeb58a96a286a/types/ember-concurrency/index.d.ts + /** + * Describes the state that the task instance is in. + * Can be used for debugging, or potentially driving some UI state. + */ export enum TaskInstanceState { + /** + * Task instance was canceled before it started. + */ Dropped = 'dropped', + + /** + * Task instance was canceled before it could finish. + */ Canceled = 'canceled', + + /** + * Task instance ran to completion, even if an exception was thrown. + */ Finished = 'finished', + + /** + * Task instance is currently running. Returns `true`, even if it is paused + * on a yielded promise. + */ Running = 'running', + + /** + * Task instance hasn't begun running yet. Usually because the task is using + * the `.enqueue()` task modifier. + */ Waiting = 'waiting' } - export interface TaskInstance extends PromiseLike, Getter { - readonly error?: unknown; + /** + * A `TaskInstance` represents a single execution of a `Task`. Every call to + * `Task#perform` returns a `TaskInstance`. + * + * `TaskInstance`s are cancelable, either explicitly via `TaskInstance#cancel` + * or `Task#cancelAll`, or automatically due to the host object being + * destroyed, or because concurrency policy enforced by a Task Modifier + * canceled the task instance. + */ + interface TaskInstanceBase extends PromiseLike, Getter { + /** + * Describes the state that the task instance is in. + * Can be used for debugging, or potentially driving some UI state. + */ + // readonly state: TaskInstanceState; + + /** + * `true` if the task instance has started, else `false`. + */ readonly hasStarted: boolean; + + /** + * `true` if the task instance was canceled before it could run to + * completion. + */ readonly isCanceled: boolean; + + /** + * `true` if the `TaskInstance` was canceled before it could ever start + * running. + * + * For example, calling `.perform()` twice on a task with the `.drop()` + * modifier applied will result in the second task instance being dropped. + */ readonly isDropped: boolean; - readonly isError: boolean; + + /** + * `true` if the task has run to completion. + */ readonly isFinished: boolean; + + /** + * `true` if the task is still running. + */ readonly isRunning: boolean; + + /** + * `true` if the task instance is fulfilled. + */ readonly isSuccessful: boolean; - readonly state: TaskInstanceState; + + /** + * If this `TaskInstance` runs to completion by returning a value other than + * a rejecting promise, this property will be set with that value. + */ readonly value?: T; + + /** + * If this `TaskInstance` is canceled or throws an error (or yields a + * promise that rejects), this property will be set with that error. + * + * Otherwise, it is `null`. + */ + readonly error?: Error; + + /** + * Cancels the task instance. Has no effect if the task instance has already + * been canceled or has already finished running. + */ cancel(): void; + catch(): RSVP.Promise; finally(): RSVP.Promise; then( @@ -114,27 +225,216 @@ declare module 'ember-concurrency' { ): RSVP.Promise; } + /** + * Task instance was canceled before it started. + */ + interface InstanceDropped { + readonly state: TaskInstanceState.Dropped; + readonly hasStarted: false; + readonly isCanceled: true; + readonly isDropped: true; + readonly isFinished: false; + readonly isRunning: false; + readonly isSuccessful: false; + readonly value: undefined; + readonly error: Error; + } + + /** + * Task instance was canceled before it could finish. + */ + interface InstanceCanceled { + readonly state: TaskInstanceState.Canceled; + readonly hasStarted: true; + readonly isCanceled: true; + readonly isDropped: false; + readonly isFinished: false; + readonly isRunning: false; + readonly isSuccessful: false; + readonly value: undefined; + readonly error: Error; + } + + /** + * Task instance is currently running, even if it is paused on a yielded + * promise. + */ + interface InstanceRunning { + readonly state: TaskInstanceState.Running; + readonly hasStarted: true; + readonly isCanceled: false; + readonly isDropped: false; + readonly isFinished: false; + readonly isRunning: true; + readonly isSuccessful: false; + readonly value: undefined; + readonly error: null; + } + + /** + * Task instance hasn't begun running yet. Usually because the task is using + * the `.enqueue()` task modifier. + */ + interface InstanceWaiting { + readonly state: TaskInstanceState.Waiting; + readonly hasStarted: false; + readonly isCanceled: false; + readonly isDropped: false; + readonly isFinished: false; + readonly isRunning: false; + readonly isSuccessful: false; + readonly value: undefined; + readonly error: null; + } + + /** + * Task instance ran to completion, but an exception was thrown. + */ + interface InstanceError { + readonly state: TaskInstanceState.Finished; + readonly hasStarted: true; + readonly isCanceled: false; + readonly isDropped: false; + readonly isFinished: true; + readonly isRunning: false; + readonly isSuccessful: false; + readonly value: undefined; + readonly error: Error; + } + + /** + * Task instance ran to completion successfully, without any exception being + * thrown. + */ + interface InstanceSuccess { + readonly state: TaskInstanceState.Finished; + readonly hasStarted: true; + readonly isCanceled: false; + readonly isDropped: false; + readonly isFinished: true; + readonly isRunning: false; + readonly isSuccessful: true; + readonly value: any; + readonly error: null; + } + + export type TaskInstance = TaskInstanceBase & + ( + | InstanceDropped + | InstanceCanceled + | InstanceRunning + | InstanceWaiting + | InstanceError + | InstanceSuccess); + + /** + * The current state of the task. + */ export enum TaskState { Running = 'running', Queued = 'queued', Idle = 'idle' } + /** + * The `Task` object lives on a host Ember object (e.g. a `Component`, + * `Route`, or `Controller`). + * + * You call the `.perform()` method on this object to run individual + * `TaskInstances`, and at any point, you can call the `.cancelAll()` method + * on this object to cancel all running or enqueued `TaskInstance`s. + */ export interface Task extends Getter { + /** + * `true` if the task is not in the `running` or `queued` state. + */ readonly isIdle: boolean; + + /** + * `true` if any future task instances are queued. + */ readonly isQueued: boolean; + + /** + * `true` if any current task instances are running. + */ readonly isRunning: boolean; + + /** + * The most recently started task instance. + */ readonly last?: TaskInstance; + + /** + * The most recently canceled task instance. + */ readonly lastCanceled?: TaskInstance; + + /** + * The most recently completed task instance. + * + * This can be a successful task instance or a task instance that threw an + * error, but was *not* canceled. + */ readonly lastComplete?: TaskInstance; + + /** + * The most recent task instance that errored. + */ readonly lastErrored?: TaskInstance; + + /** + * The most recent task instance that is incomplete. + * + * @TODO: difference between `last`, `lastPerformed`, `lastIncomplete`? + */ readonly lastIncomplete?: TaskInstance; + + /** + * The most recently performed task instance. + */ readonly lastPerformed?: TaskInstance; + + /** + * The most recent task instance that is currently running. + */ readonly lastRunning?: TaskInstance; + + /** + * The most recent task instance that succeeded. + */ readonly lastSuccessful?: TaskInstance; + + /** + * The number of times this task has been performed. + */ readonly performCount: number; + + /** + * The current state of the task. + * + * @TODO: what is the state when `.enqueue()` is used and one `TaskInstance` + * is running, while the next is queued? + */ readonly state: TaskState; + + /** + * Creates a new `TaskInstance` and attempts to run it right away. + * + * If running this task instance would increase the task's concurrency to a + * number greater than the task's `maxConcurrency`, this task instance might + * be immediately canceled (dropped), or enqueued to run at a later time, + * after the currently running task(s) have finished. + */ perform(...args: Args): TaskInstance; + + /** + * Cancels all running or queued `TaskInstance`s for this `Task`. + * + * If you're trying to cancel a specific `TaskInstance` (rather than all of + * the instances running under this task) call `.cancel()` on the specific + * `TaskInstance`. + */ cancelAll(): void; } } From dc550f22d94a1ca056be8e8022f8c294ab4e6c87 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jan=20Buscht=C3=B6ns?= Date: Tue, 13 Aug 2019 20:43:40 +0200 Subject: [PATCH 7/7] v1.1.0-alpha.1 --- package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/package.json b/package.json index f48db72..4142f1a 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "ember-concurrency-decorators", - "version": "1.0.0-beta.4", + "version": "1.1.0-alpha.1", "description": "decorator syntax for declaring/configuring ember-concurrency tasks", "keywords": [ "ember-addon"