From 824404911d82185229c7b33e322789ac9a171154 Mon Sep 17 00:00:00 2001 From: NullVoxPopuli Date: Wed, 26 Oct 2022 23:57:19 -0400 Subject: [PATCH] feat(trackedFunction): add a retry method --- ember-resources/src/util/function.ts | 59 ++++++++++++------- .../utils/function-resource/rendering-test.ts | 2 - .../tests/utils/function/rendering-test.ts | 31 ++++++++++ 3 files changed, 70 insertions(+), 22 deletions(-) diff --git a/ember-resources/src/util/function.ts b/ember-resources/src/util/function.ts index 8236f2e03..6526536b3 100644 --- a/ember-resources/src/util/function.ts +++ b/ember-resources/src/util/function.ts @@ -126,25 +126,9 @@ export function trackedFunction(...passedArgs: UseFunctionArgs) } return resource>(context, (hooks) => { - let state = new State(initialValue); + let state = new State(fn, hooks, initialValue); - (async () => { - try { - let notQuiteValue = fn(hooks); - let promise = Promise.resolve(notQuiteValue); - - waitForPromise(promise); - - let result = await promise; - - state.error = undefined; - state.resolvedValue = result; - } catch (e) { - state.error = e; - } finally { - state.isResolved = true; - } - })(); + state.retry(); return state; }); @@ -158,10 +142,18 @@ export class State { @tracked resolvedValue?: Value; @tracked error?: unknown; - constructor(public initialValue?: Value) {} + #fn: ResourceFn; + #hooks: Hooks; + #initialValue: Value | undefined; + + constructor(fn: ResourceFn, hooks: Hooks, initialValue?: Value) { + this.#fn = fn; + this.#hooks = hooks; + this.#initialValue = initialValue; + } get value() { - return this.resolvedValue || this.initialValue || null; + return this.resolvedValue || this.#initialValue || null; } get isPending() { @@ -175,6 +167,33 @@ export class State { get isError() { return Boolean(this.error); } + + /** + * Will re-invoke the function passed to `trackedFunction` + * this will also re-set some properties on the `State` instance. + * This is the same `State` instance as before, as the `State` instance + * is tied to the `fn` passed to `trackedFunction` + * + * `error` or `resolvedValue` will remain as they were previously + * until this promise resolves, and then they'll be updated to the new values. + */ + retry = async () => { + try { + let notQuiteValue = this.#fn(this.#hooks); + let promise = Promise.resolve(notQuiteValue); + + waitForPromise(promise); + + let result = await promise; + + this.error = undefined; + this.resolvedValue = result; + } catch (e) { + this.error = e; + } finally { + this.isResolved = true; + } + }; } /** diff --git a/testing/ember-app/tests/utils/function-resource/rendering-test.ts b/testing/ember-app/tests/utils/function-resource/rendering-test.ts index 3d7e0f923..bbe92008e 100644 --- a/testing/ember-app/tests/utils/function-resource/rendering-test.ts +++ b/testing/ember-app/tests/utils/function-resource/rendering-test.ts @@ -435,8 +435,6 @@ module('Utils | resource | rendering', function (hooks) { }); }); - module('persistent state', function () {}); - module('with a wrapper', function () { test('lifecycle', async function (assert) { const Wrapper = (initial: number) => { diff --git a/testing/ember-app/tests/utils/function/rendering-test.ts b/testing/ember-app/tests/utils/function/rendering-test.ts index a6461a7ab..5ec119455 100644 --- a/testing/ember-app/tests/utils/function/rendering-test.ts +++ b/testing/ember-app/tests/utils/function/rendering-test.ts @@ -40,6 +40,37 @@ module('Utils | trackedFunction | rendering', function (hooks) { assert.dom('out').hasText('2'); }); + test('it is retryable', async function (assert) { + let count = 0; + + class Test extends Component { + data = trackedFunction(this, () => { + assert.step(`ran trackedFunction ${count++}`); + + return count; + }); + } + + const TestComponent = setComponentTemplate( + hbs` + {{this.data.value}} + `, + Test + ); + + this.setProperties({ TestComponent }); + + await render(hbs``); + + assert.dom('out').hasText('1'); + + await click('button'); + + assert.dom('out').hasText('2'); + + assert.verifySteps(['ran trackedFunction 0', 'ran trackedFunction 1']); + }); + test('async functions update when the promise resolves', async function (assert) { class Test extends Component { @tracked multiplier = 1;