Skip to content

Conversation

luisherranz
Copy link
Collaborator

What?

This PR introduces two new TypeScript helper types, AsyncAction<ReturnType> and TypeYield<T>, to the Interactivity API.

It also adds documentation for both helpers in the "Using TypeScript" guide, explaining their purpose and how to use them to solve common typing issues with asynchronous actions (generators).

Why?

When working with asynchronous actions (defined as generators) in the Interactivity API and TypeScript, developers can encounter two main issues:

  1. Circular References: If state is used within a yield expression or if the generator's return value depends on state, TypeScript can struggle to infer types, leading to circular reference errors or types defaulting to any.
  2. Typing Yielded Values: Even if the overall generator type is correctly inferred or specified, the actual value resolved by an individual yield expression (e.g., the result of a fetched promise) is not accurately typed within the generator's scope, defaulting to any.

How?

  • AsyncAction<ReturnType>: Defined as Generator<any, ReturnType, unknown>.

    This helper allows developers to explicitly type the return value of an asynchronous action (generator). By using any for the yielded values, it helps break circular type dependencies when state is used within yield expressions or in the final return value.

  • TypeYield<T extends (...args: any[]) => Promise<any>>: Defined as Awaited<ReturnType<T>>.

    This helper allows developers to explicitly type the value that a yield expression resolves to, by providing the type of the async function/operation being yielded.

@luisherranz luisherranz requested a review from DAreRodz as a code owner May 14, 2025 15:00
Copy link

github-actions bot commented May 14, 2025

The following accounts have interacted with this PR and/or linked issues. I will continue to update these lists as activity occurs. You can also manually ask me to refresh this list by adding the props-bot label.

If you're merging code through a pull request on GitHub, copy and paste the following into the bottom of the merge commit message.

Co-authored-by: luisherranz <luisherranz@git.wordpress.org>
Co-authored-by: DAreRodz <darerodz@git.wordpress.org>

To understand the WordPress project's expectations around crediting contributors, please review the Contributor Attribution page in the Core Handbook.

Copy link
Collaborator

@DAreRodz DAreRodz left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

LGTM! ✅

Just left some minor comments. 👇

PS: TypeYield` is not easy to improve with the current state of TypeScript, right? I guess there's no other way to infer the returned type of an async function executed inside a generator.

@luisherranz
Copy link
Collaborator Author

luisherranz commented May 20, 2025

PS: TypeYield` is not easy to improve with the current state of TypeScript, right?

My initial idea was to use a function.

/**
 * Creates a generator that yields a promise and returns its resolved value.
 *
 * This utility function is used to convert promises into generators, which
 * can be useful when working with asynchronous actions.
 *
 * @since 6.7.0
 *
 * @param promise The promise to be converted into a generator.
 *
 * @return A generator that yields the promise and returns its resolved value.
 */
export function typed< T >(
	promise: Promise< T >
): Generator< Promise< T >, T, T > {
	return ( function* () {
		return yield promise;
	} )();
}
*asyncAction( n: number ) {
  const n1 = yield* typed( myPromise( n ) );
  const n2: number = n1;
  return n2;
}

// as opposed to
*asyncAction( n: number ) {
  const n1 = ( yield myPromise( n ) ) as TypeYield< typeof myPromise >;
  const n2: number = n1;
  return n2;
}

But Jon didn't like the idea of using a JavaScript function simply to cast some types, so he deleted it from the original PR.

In my opinion, the final syntax was a bit cleaner, but I kind of agree it was not ideal (especially the need for the extra * in the yield). I wanted to avoid having to manually override the types using as, but as far as I know, there's no other way to do this.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

2 participants