diff --git a/.changeset/spicy-mugs-eat.md b/.changeset/spicy-mugs-eat.md new file mode 100644 index 0000000000..7a05b06ceb --- /dev/null +++ b/.changeset/spicy-mugs-eat.md @@ -0,0 +1,5 @@ +--- +'@lit-labs/task': patch +--- + +Infer the return type of Task.render() diff --git a/packages/labs/task/src/task.ts b/packages/labs/task/src/task.ts index 5e7f6f4322..7e24427280 100644 --- a/packages/labs/task/src/task.ts +++ b/packages/labs/task/src/task.ts @@ -10,8 +10,7 @@ export interface TaskFunctionOptions { signal?: AbortSignal; } -// eslint-disable-next-line @typescript-eslint/no-explicit-any -export type TaskFunction, R = any> = ( +export type TaskFunction, R = unknown> = ( args: D, options?: TaskFunctionOptions ) => R | typeof initialState | Promise; @@ -327,19 +326,20 @@ export class Task< return this._error; } - render(renderer: StatusRenderer) { + render>(renderer: T) { switch (this.status) { case TaskStatus.INITIAL: - return renderer.initial?.(); + return renderer.initial?.() as MaybeReturnType; case TaskStatus.PENDING: - return renderer.pending?.(); + return renderer.pending?.() as MaybeReturnType; case TaskStatus.COMPLETE: - return renderer.complete?.(this.value!); + return renderer.complete?.(this.value!) as MaybeReturnType< + T['complete'] + >; case TaskStatus.ERROR: - return renderer.error?.(this.error); + return renderer.error?.(this.error) as MaybeReturnType; default: - // exhaustiveness check - this.status as void; + throw new Error(`Unexpected status: ${this.status}`); } } @@ -351,3 +351,7 @@ export class Task< : args !== prev; } } + +type MaybeReturnType = F extends (...args: unknown[]) => infer R + ? R + : undefined; diff --git a/packages/labs/task/src/test/task_test.ts b/packages/labs/task/src/test/task_test.ts index bce98225a6..ff252ebacf 100644 --- a/packages/labs/task/src/test/task_test.ts +++ b/packages/labs/task/src/test/task_test.ts @@ -942,4 +942,31 @@ suite('Task', () => { assert.equal(el.task.value, secondValue); assert.notEqual(firstValue, secondValue); }); + + test('type-only render return type test', () => { + class TestElement extends ReactiveElement { + task = new Task(this, () => 'abc'); + } + customElements.define(generateElementName(), TestElement); + const el = new TestElement(); + + const accept = (x: T) => x; + + accept(el.task.render({initial: () => 123})); + accept(el.task.render({complete: () => 123})); + accept(el.task.render({pending: () => 123})); + accept(el.task.render({error: () => 123})); + accept( + el.task.render({initial: () => 123, complete: () => 123}) + ); + + accept( + el.task.render({ + initial: () => 123, + complete: () => 123, + pending: () => 123, + error: () => 123, + }) + ); + }); });