Skip to content

Commit

Permalink
feat(trackedFunction): add a retry method
Browse files Browse the repository at this point in the history
  • Loading branch information
NullVoxPopuli committed Oct 27, 2022
1 parent c435c17 commit 8244049
Show file tree
Hide file tree
Showing 3 changed files with 70 additions and 22 deletions.
59 changes: 39 additions & 20 deletions ember-resources/src/util/function.ts
Original file line number Diff line number Diff line change
Expand Up @@ -126,25 +126,9 @@ export function trackedFunction<Return>(...passedArgs: UseFunctionArgs<Return>)
}

return resource<State<Return>>(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;
});
Expand All @@ -158,10 +142,18 @@ export class State<Value> {
@tracked resolvedValue?: Value;
@tracked error?: unknown;

constructor(public initialValue?: Value) {}
#fn: ResourceFn<Value>;
#hooks: Hooks;
#initialValue: Value | undefined;

constructor(fn: ResourceFn<Value>, 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() {
Expand All @@ -175,6 +167,33 @@ export class State<Value> {
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;
}
};
}

/**
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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) => {
Expand Down
31 changes: 31 additions & 0 deletions testing/ember-app/tests/utils/function/rendering-test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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`
<out>{{this.data.value}}</out>
<button type='button' {{on 'click' this.data.retry}}></button>`,
Test
);

this.setProperties({ TestComponent });

await render(hbs`<this.TestComponent />`);

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;
Expand Down

0 comments on commit 8244049

Please sign in to comment.