From 090c2fe79b9ba406bcb6d7e03d102598fcf829d6 Mon Sep 17 00:00:00 2001 From: Arnav Gupta Date: Tue, 27 Feb 2018 22:16:44 +0530 Subject: [PATCH 1/3] Add typedefinitions Signed-off-by: Arnav Gupta --- package.json | 2 + types/Task.d.ts | 130 +++++++++++++++++++++++++++++++ types/ember-concurrency-tests.ts | 88 +++++++++++++++++++++ types/index.d.ts | 34 ++++++++ types/tsconfig.json | 25 ++++++ types/tslint.json | 1 + 6 files changed, 280 insertions(+) create mode 100644 types/Task.d.ts create mode 100644 types/ember-concurrency-tests.ts create mode 100644 types/index.d.ts create mode 100644 types/tsconfig.json create mode 100644 types/tslint.json diff --git a/package.json b/package.json index 47f4b0fa7..d747b1cc9 100644 --- a/package.json +++ b/package.json @@ -6,6 +6,8 @@ "doc": "doc", "test": "tests" }, + "types": "types/index.d.ts", + "typings": "types/index.d.ts", "scripts": { "build": "ember build", "start": "ember serve", diff --git a/types/Task.d.ts b/types/Task.d.ts new file mode 100644 index 000000000..98799053b --- /dev/null +++ b/types/Task.d.ts @@ -0,0 +1,130 @@ +import RSVP from "rsvp"; + +export class TaskProperty { + /** + * This behaves like the {@linkcode TaskProperty#on task(...).on() modifier}, + * but instead will cause the task to be canceled if any of the + * specified events fire on the parent object. + * + * [See the Live Example](/#/docs/examples/route-tasks/1) + * + */ + cancelOn(...eventNames: string[]): this; + + /** + * Logs lifecycle events to aid in debugging unexpected + * Task behavior. Presently only logs cancelation events + * and the reason for the cancelation, e.g. "TaskInstance + * 'doStuff' was canceled because the object it lives on + * was destroyed or unrendered" + */ + debug(): void; + drop(): void; + enqueue(): void; + group(groupPath: any): any; + keepLatest(): void; + maxConcurrency(n: number): void; + on(...eventNames: string[]): this; + restartable(): void; +} + +export class TaskInstance { + /** 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; + + /** 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 isCancelled: 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; + + /** True if the task instance resolves to a rejection. */ + 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; + + /** Describes the state that the task instance is in. Can be used for debugging, or potentially driving some UI state. */ + readonly state: 'dropped' | 'cancelled' | 'finished' | 'running' | 'waiting'; + + /** If this TaskInstance runs to completion by returning a property other than a rejecting promise, this property will be set with that value. */ + readonly value: any; + + /** 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(): RSVP.Promise; +} + +export class Task { + /** 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 lastCancelled?: TaskInstance; + + /** The most recently completed task instance. */ + readonly lastComplete?: TaskInstance; + + /** The most recent task instance that errored. */ + readonly lastErrored?: TaskInstance; + + /** The most recent task instance that is incomplete. */ + 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 lastSucessful?: TaskInstance; + + /** The number of times this task has been performed. */ + readonly performCount: number; + + /** The current state of the task: "running", "queued" or "idle". */ + readonly state: 'running' | 'queued' | 'idle'; + + /** + * Cancels all running or queued TaskInstances 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; + + /** + * 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 later + * time, after the currently running task(s) have finished. + * @param arg args to pass to the task function + */ + perform(...arg: any[]): void; +} diff --git a/types/ember-concurrency-tests.ts b/types/ember-concurrency-tests.ts new file mode 100644 index 000000000..1b29e9f8f --- /dev/null +++ b/types/ember-concurrency-tests.ts @@ -0,0 +1,88 @@ +import 'jquery'; +import { + task, + timeout, + waitForEvent, + taskGroup, + waitForProperty, + waitForQueue +} from 'ember-concurrency'; +import Ember from 'ember'; +import Controller from '@ember/controller'; + +Ember.Component.extend({ + loopingTask: task(function *(this: Ember.Component) { + while (true) { + this.set('num', Math.random()); + yield timeout(100); + } + }).on('init') +}); + +Ember.Component.extend({ + myTask: task(function *(this: any) { + const clickEvent = yield waitForEvent($('body'), 'click'); + const emberEvent = yield waitForEvent(this, 'foo'); + // somewhere else: component.trigger('foo', { value: 123 }); + }) +}); + +Ember.Component.extend({ + myTask: task(function *() { + console.log("Pausing for a second..."); + yield timeout(1000); + console.log("Done!"); + }) +}); +function* taskFn() { yield 1; } + +Controller.extend({ + chores: taskGroup().drop(), + + mowLawn: task(taskFn).group('chores'), + doDishes: task(taskFn).group('chores'), + changeDiapers: task(taskFn).group('chores') +}); + +Ember.Component.extend({ + myTask: task(function *() { + while (true) { + console.log("Hello!"); + yield timeout(1000); + } + }) +}); + +Ember.Component.extend({ + myTask: task(function *(this: Ember.Evented) { + console.log("Please click anywhere.."); + const clickEvent = yield waitForEvent($('body'), 'click'); + console.log("Got event", clickEvent); + + const emberEvent = yield waitForEvent(this, 'foo'); + console.log("Got foo event", emberEvent); + + // somewhere else: component.trigger('foo', { value: 123 }); + }) +}); + +Ember.Component.extend({ + foo: 0, + + myTask: task(function *(this: any) { + console.log("Waiting for `foo` to become 5"); + + yield waitForProperty(this, 'foo', v => v === 5); + + // somewhere else: this.set('foo', 5) + + console.log("`foo` is 5!"); + }) +}); + +Ember.Component.extend({ + myTask: task(function *() { + yield waitForQueue('afterRender'); + console.log("now we're in the afterRender queue"); + }) +}); diff --git a/types/index.d.ts b/types/index.d.ts new file mode 100644 index 000000000..db480ad88 --- /dev/null +++ b/types/index.d.ts @@ -0,0 +1,34 @@ +// Type definitions for ember-concurrency 0.8 +// Project: https://github.com/machty/ember-concurrency#readme +// Definitions by: Arnav Gupta +// Definitions: https://github.com/DefinitelyTyped/DefinitelyTyped +// TypeScript Version: 2.4 + +import Evented from '@ember/object/evented'; +import RSVP from 'rsvp'; +import { Task, TaskProperty, TaskInstance } from './Task'; + +export { Task, TaskProperty, TaskInstance }; +export const all: typeof RSVP.all; +export const allSettled: typeof RSVP.allSettled; +export const hash: typeof RSVP.hash; +export const race: typeof RSVP.race; + +/** + * Returns true if the object passed to it is a + * TaskCancelation error. If you call someTask.perform().catch(...) + * or otherwise treat a TaskInstance like a promise, you may need + * to handle the cancelation of a TaskInstance differently from + * other kinds of errors it might throw, and you can use this + * convenience function to distinguish cancelation from errors. + * + * @param {Error} error + * @returns {boolean} + */ +export function didCancel(error?: Error): boolean; +export function task(generatorFunction: (...args: any[]) => Generator): TaskProperty; +export function taskGroup(...args: any[]): any; +export function timeout(ms: number): any; +export function waitForEvent(object: Evented | RSVP.EventTarget | EventTarget, eventName: string): any; +export function waitForProperty(object: any, key: string, callback: (...args: any[]) => any): any; +export function waitForQueue(queueName: string): any; diff --git a/types/tsconfig.json b/types/tsconfig.json new file mode 100644 index 000000000..cc00ad2f4 --- /dev/null +++ b/types/tsconfig.json @@ -0,0 +1,25 @@ +{ + "compilerOptions": { + "module": "commonjs", + "lib": [ + "es2015", + "dom" + ], + "target": "es2015", + "noImplicitAny": true, + "noImplicitThis": true, + "strictFunctionTypes": true, + "strictNullChecks": true, + "baseUrl": "../", + "typeRoots": [ + "../" + ], + "types": [], + "noEmit": true, + "forceConsistentCasingInFileNames": true + }, + "files": [ + "index.d.ts", + "ember-concurrency-tests.ts" + ] +} diff --git a/types/tslint.json b/types/tslint.json new file mode 100644 index 000000000..3db14f85e --- /dev/null +++ b/types/tslint.json @@ -0,0 +1 @@ +{ "extends": "dtslint/dt.json" } From 46df8d358b5ff43059e31075eaef5df6fa010c47 Mon Sep 17 00:00:00 2001 From: Arnav Gupta Date: Sat, 3 Mar 2018 02:22:14 +0530 Subject: [PATCH 2/3] depend on ember types Signed-off-by: Arnav Gupta --- package.json | 2 ++ types/tslint.json | 1 - 2 files changed, 2 insertions(+), 1 deletion(-) delete mode 100644 types/tslint.json diff --git a/package.json b/package.json index d747b1cc9..a65cb1c22 100644 --- a/package.json +++ b/package.json @@ -20,6 +20,8 @@ "author": "Alex Matchneer ", "license": "MIT", "devDependencies": { + "@types/ember": "^2.8.15", + "@types/rsvp": "^4.0.1", "broccoli-asset-rev": "https://github.com/ef4/broccoli-asset-rev#838f1b7186fde566d3e8010196c1f974423a4946", "broccoli-clean-css": "^1.1.0", "documentation": "^3.0.4", diff --git a/types/tslint.json b/types/tslint.json deleted file mode 100644 index 3db14f85e..000000000 --- a/types/tslint.json +++ /dev/null @@ -1 +0,0 @@ -{ "extends": "dtslint/dt.json" } From 537ef80b64942362f798a5b42376e9ec98f5e0d1 Mon Sep 17 00:00:00 2001 From: Arnav Gupta Date: Sat, 3 Mar 2018 02:23:10 +0530 Subject: [PATCH 3/3] update to more correct types Signed-off-by: Arnav Gupta --- types/Task.d.ts | 61 ++++++++++++++++++++++-------------------------- types/index.d.ts | 6 ++--- 2 files changed, 31 insertions(+), 36 deletions(-) diff --git a/types/Task.d.ts b/types/Task.d.ts index 98799053b..4e4518e7d 100644 --- a/types/Task.d.ts +++ b/types/Task.d.ts @@ -1,6 +1,7 @@ import RSVP from "rsvp"; +import ComputedProperty from '@ember/object/computed' -export class TaskProperty { +export class TaskProperty extends ComputedProperty{ /** * This behaves like the {@linkcode TaskProperty#on task(...).on() modifier}, * but instead will cause the task to be canceled if any of the @@ -18,61 +19,55 @@ export class TaskProperty { * 'doStuff' was canceled because the object it lives on * was destroyed or unrendered" */ - debug(): void; - drop(): void; - enqueue(): void; - group(groupPath: any): any; - keepLatest(): void; - maxConcurrency(n: number): void; + debug(): this; + drop(): this; + enqueue(): this; + group(groupPath: any): this; + keepLatest(): this; + maxConcurrency(n: number): this; on(...eventNames: string[]): this; - restartable(): void; + restartable(): this; } - -export class TaskInstance { +export type TaskState = 'dropped' | 'cancelled' | 'finished' | 'running' | 'waiting'; +export class TaskInstance extends RSVP.Promise { /** 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; /** True if the task instance has started, else false. */ - readonly hasStarted: boolean; + readonly hasStarted: ComputedProperty; /** True if the task instance was canceled before it could run to completion. */ - readonly isCancelled: boolean; + readonly isCancelled: ComputedProperty; /** 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 isDropped?: ComputedProperty; /** True if the task instance resolves to a rejection. */ - readonly isError?: boolean; + readonly isError?: ComputedProperty; /** True if the task has run to completion. */ - readonly isFinished: boolean; + readonly isFinished: ComputedProperty; /** True if the task is still running. */ - readonly isRunning: boolean; + readonly isRunning: ComputedProperty; /** True if the task instance is fulfilled. */ readonly isSuccessful: boolean; /** Describes the state that the task instance is in. Can be used for debugging, or potentially driving some UI state. */ - readonly state: 'dropped' | 'cancelled' | 'finished' | 'running' | 'waiting'; + readonly state: TaskState; /** If this TaskInstance runs to completion by returning a property other than a rejecting promise, this property will be set with that value. */ - readonly value: any; + readonly value: T; /** 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(): RSVP.Promise; } -export class Task { +export class Task extends TaskProperty { /** true if the task is not in the running or queued state. */ readonly isIdle: boolean; /** true if any future task instances are queued. */ @@ -81,28 +76,28 @@ export class Task { readonly isRunning: boolean; /** The most recently started task instance. */ - readonly last?: TaskInstance; + readonly last?: TaskInstance; /** The most recently canceled task instance. */ - readonly lastCancelled?: TaskInstance; + readonly lastCancelled?: TaskInstance; /** The most recently completed task instance. */ - readonly lastComplete?: TaskInstance; + readonly lastComplete?: TaskInstance; /** The most recent task instance that errored. */ - readonly lastErrored?: TaskInstance; + readonly lastErrored?: TaskInstance; /** The most recent task instance that is incomplete. */ - readonly lastIncomplete?: TaskInstance; + readonly lastIncomplete?: TaskInstance; /** The most recently performed task instance. */ - readonly lastPerformed?: TaskInstance; + readonly lastPerformed?: TaskInstance; /** The most recent task instance that is currently running. */ - readonly lastRunning?: TaskInstance; + readonly lastRunning?: TaskInstance; /** The most recent task instance that succeeded. */ - readonly lastSucessful?: TaskInstance; + readonly lastSucessful?: TaskInstance; /** The number of times this task has been performed. */ readonly performCount: number; diff --git a/types/index.d.ts b/types/index.d.ts index db480ad88..62efa670c 100644 --- a/types/index.d.ts +++ b/types/index.d.ts @@ -4,7 +4,7 @@ // Definitions: https://github.com/DefinitelyTyped/DefinitelyTyped // TypeScript Version: 2.4 -import Evented from '@ember/object/evented'; +import Ember from 'ember'; import RSVP from 'rsvp'; import { Task, TaskProperty, TaskInstance } from './Task'; @@ -26,9 +26,9 @@ export const race: typeof RSVP.race; * @returns {boolean} */ export function didCancel(error?: Error): boolean; -export function task(generatorFunction: (...args: any[]) => Generator): TaskProperty; +export function task (generatorFunction: (...args: any[]) => Generator): TaskProperty; export function taskGroup(...args: any[]): any; export function timeout(ms: number): any; -export function waitForEvent(object: Evented | RSVP.EventTarget | EventTarget, eventName: string): any; +export function waitForEvent(object: Ember.Evented | RSVP.EventTarget | EventTarget, eventName: string): any; export function waitForProperty(object: any, key: string, callback: (...args: any[]) => any): any; export function waitForQueue(queueName: string): any;