diff --git a/ember-resources/src/util/function.ts b/ember-resources/src/util/function.ts index e6f0e9f0b..3dc780445 100644 --- a/ember-resources/src/util/function.ts +++ b/ember-resources/src/util/function.ts @@ -1,5 +1,6 @@ import { tracked } from '@glimmer/tracking'; import { assert } from '@ember/debug'; +import { action } from '@ember/object'; import { waitForPromise } from '@ember/test-waiters'; import { resource } from '../core/function-based'; @@ -92,43 +93,53 @@ export function trackedFunction(...passedArgs: UseFunctionArgs) fn = passedArgs[2]; } - return resource>(context, (hooks) => { - let state = new State(initialValue); + return resource>(context, (hooks) => { + return new TrackedFunctionProperty(fn, hooks, initialValue); + }); +} - (async () => { - try { - let notQuiteValue = fn(hooks); - let promise = Promise.resolve(notQuiteValue); +export class TrackedFunctionProperty { + @tracked state: State; - waitForPromise(promise); + constructor(private fn: ResourceFn, private hooks: Hooks, initialValue?: Value) { + this.state = new State(initialValue); + this.getValue(); + } - let result = await promise; + async getValue() { + try { + let notQuiteValue = this.fn(this.hooks); + let promise = Promise.resolve(notQuiteValue); - state.error = undefined; - state.resolvedValue = result; - } catch (e) { - state.error = e; - } finally { - state.isResolved = true; - } - })(); + waitForPromise(promise); - return state; - }); -} + let result = await promise; -/** - * State container that represents the asynchrony of a `trackedFunction` - */ -export class State { - @tracked isResolved = false; - @tracked resolvedValue?: Value; - @tracked error?: unknown; + this.state.error = undefined; + this.state.resolvedValue = result; + } catch (e) { + this.state.error = e; + } finally { + this.state.isResolved = true; + } + } - constructor(public initialValue?: Value) {} + @action + execute() { + this.state = new State(this.state.initialValue); + this.getValue(); + + return this.state; + } + get isResolved() { + return this.state.isResolved; + } + get error() { + return this.state.error; + } get value() { - return this.resolvedValue || this.initialValue || null; + return this.state.resolvedValue || this.state.initialValue || null; } get isPending() { @@ -144,6 +155,17 @@ export class State { } } +/** + * State container that represents the asynchrony of a `trackedFunction` + */ +export class State { + @tracked isResolved = false; + @tracked resolvedValue?: Value; + @tracked error?: unknown; + + constructor(public initialValue?: Value) {} +} + /** * @private * diff --git a/testing/ember-app/tests/utils/function/js-test.ts b/testing/ember-app/tests/utils/function/js-test.ts index 572d68bf7..3145d303e 100644 --- a/testing/ember-app/tests/utils/function/js-test.ts +++ b/testing/ember-app/tests/utils/function/js-test.ts @@ -149,4 +149,47 @@ module('Utils | trackedFunction | js', function (hooks) { assert.strictEqual(foo.data.value, 2); }); + + test('it can be reexecuted', async function (assert) { + const Store = { + data: [[{ id: 1 }], [{ id: 1 }, { id: 2 }], [{ id: 1 }], [{ id: 1 }]], + fetch(page: number) { + const value = this.data.shift(); + + return value?.map((v) => ({ ...v, page })); + }, + }; + + class Test { + store = Store; + @tracked page = 1; + + @tracked data = trackedFunction(this, () => { + return this.store.fetch(this.page); + }); + } + + let foo = new Test(); + + foo.data.value; + await settled(); + assert.deepEqual(foo.data.value, [{ id: 1, page: 1 }]); + + foo.page = 2; + foo.data.value; + await settled(); + assert.deepEqual(foo.data.value, [ + { id: 1, page: 2 }, + { id: 2, page: 2 }, + ]); + + const value = await foo.data.execute(); + + await settled(); + assert.deepEqual(value.resolvedValue, [{ id: 1, page: 2 }]); + + await foo.data.execute(); + await settled(); + assert.deepEqual(foo.data.value, [{ id: 1, page: 2 }]); + }); });