diff --git a/reactiveweb/src/ember-concurrency.ts b/reactiveweb/src/ember-concurrency.ts index 339db81..016b97d 100644 --- a/reactiveweb/src/ember-concurrency.ts +++ b/reactiveweb/src/ember-concurrency.ts @@ -53,14 +53,10 @@ export function task< >(context: object, task: LocalTask, thunk?: () => Args) { assert(`Task does not have a perform method. Is it actually a task?`, 'perform' in task); - const state = new State(task); + const state = new State(task, thunk); let destroyable = resource(context, () => { - let args = thunk || DEFAULT_THUNK; - - let positional = normalizeThunk(args).positional as Args; - - state[RUN](positional || []); + state[RUN](); return state; }); @@ -69,7 +65,7 @@ export function task< registerDestructor(state, () => state[TASK].cancelAll()); - return destroyable as unknown as TaskInstance; + return destroyable as unknown as TaskInstance & { retry: () => void }; } export const trackedTask = task; @@ -110,6 +106,7 @@ export interface TaskInstance extends Promise { export const TASK = Symbol('TASK'); const RUN = Symbol('RUN'); +const THUNK = Symbol('THUNK'); /** * @private @@ -117,9 +114,11 @@ const RUN = Symbol('RUN'); export class State> { // Set via useTask declare [TASK]: LocalTask; + declare [THUNK]?: () => Args; - constructor(task: LocalTask) { + constructor(task: LocalTask, thunk?: () => Args) { this[TASK] = task; + this[THUNK] = thunk; // eslint-disable-next-line @typescript-eslint/no-this-alias const self = this; @@ -161,6 +160,10 @@ export class State { + [RUN] = () => { + let args = this[THUNK] || DEFAULT_THUNK; + + let positional = (normalizeThunk(args).positional ?? []) as Args; + if (this.currentTask) { this.lastTask = this.currentTask; } this.currentTask = this[TASK].perform(...positional); }; + + /** + * Will re-invoke the task passed to `trackedTask` + */ + retry = () => { + this[RUN](); + }; } diff --git a/tests/test-app/tests/utils/ember-concurrency/js-test.ts b/tests/test-app/tests/utils/ember-concurrency/js-test.ts index aa55bfe..2a2faef 100644 --- a/tests/test-app/tests/utils/ember-concurrency/js-test.ts +++ b/tests/test-app/tests/utils/ember-concurrency/js-test.ts @@ -195,6 +195,72 @@ module('useTask', function () { assert.true(foo.search.isFinished); assert.false(foo.search.isRunning); }); + + test('it runs again when calling retry()', async function (assert) { + class Test { + @tracked input = ''; + + counter = 0; + + _search = restartableTask(async (input: string) => { + // or some bigger timeout for an actual search task to debounce + await timeout(0); + + this.counter++; + + // or some api data if actual search task + return { results: [input, this.counter] }; + }); + + search = trackedTask(this, this._search, () => [this.input]); + } + + let foo = new Test(); + + // task is initiated upon first access + foo.search; + await settled(); + + + assert.strictEqual(foo.search.value, undefined); + assert.false(foo.search.isFinished); + assert.true(foo.search.isRunning); + + await settled(); + + assert.true(foo.search.isFinished); + assert.false(foo.search.isRunning); + assert.deepEqual(foo.search.value, { results: ['', 1] }); + assert.strictEqual(foo._search.performCount, 1, 'Task was performed once'); + + foo.input = 'Hello there!'; + await settled(); + + assert.deepEqual(foo.search.value, { results: ['', 1] }, 'previous value is retained'); + assert.false(foo.search.isFinished); + assert.true(foo.search.isRunning); + + await settled(); + + assert.true(foo.search.isFinished); + assert.false(foo.search.isRunning); + assert.deepEqual(foo.search.value, { results: ['Hello there!', 2] }); + assert.strictEqual(foo._search.performCount, 2, 'Task was performed twice'); + + + foo.search.retry(); + + assert.deepEqual(foo.search.value, { results: ['Hello there!', 2] }, 'previous value is retained'); + assert.false(foo.search.isFinished); + assert.true(foo.search.isRunning); + + await settled(); + assert.true(foo.search.isFinished); + assert.false(foo.search.isRunning); + assert.deepEqual(foo.search.value, { results: ['Hello there!', 3] }); + + assert.strictEqual(foo._search.performCount, 3, 'Task was performed three times'); + }); }); }); });