Deducing the return type of asynchronously generated functions

My expectation is to automatically determine the return type of async functions when they are yielded as values from a generator.

In the following example, the inference of the type of the yielded async functions appears to work correctly (detected as () => Promise<string>). However, the variable T cannot be inferred as string and ends up being unknown.

I have simplified a complex issue to the example shown below (refer to playground). This example includes all the necessary features for the actual problem I am facing.

The commented code provides additional information about the need to infer both J and T. Importantly, extensions to Job<T> can contain job-specific metadata.

Could someone figure out how to construct type definitions that can successfully infer both J and T in the given scenario, so that the settlement type becomes

JobSettlement<string, () => Promise<string>>
instead of
JobSettlement<unknown, () => Promise<string>>

https://i.sstatic.net/ZMN04.png

I suspect this is one of the unresolved cases mentioned in microsoft/TypeScript#47599, but I'm unsure how to reconfigure it so TypeScript can handle the inference.

type Job<T> = () => Promise<T>

export interface JobFulfilment<T, J extends Job<T>> {
  job: J;
  status: "fulfilled";
  value: T;
}

export interface JobRejection<T, J extends Job<T>> {
  job: J;
  status: "rejected";
  reason: unknown;
}

export type JobSettlement<T, J extends Job<T>> =
  | JobFulfilment<T, J>
  | JobRejection<T, J>;

export async function* createSettlementSequence<T, J extends Job<T>>(
  createJobSequence: () => AsyncGenerator<J>
): AsyncIterable<JobSettlement<T, J>> {
    for await (const job of createJobSequence()){
        try{
            const value = await job();
            yield {
                job,
                value,
                status:"fulfilled",
            }
        }
        catch(reason){
            yield {
                job,
                reason,
                status:"rejected"
            }
        }
    }
}

const settlementSequence = createSettlementSequence(async function* () {
    yield async () => "foo"
    yield async () => "bar"
    yield async () => "baz"
})


// const settlementSequenceWithMetadata = createSettlementSequence(async function* () {
//     yield Object.assign(async () => "foo", { task: 0})
//     yield Object.assign(async () => "bar", { task: 1})
//     yield Object.assign(async () => "baz", { task: 2})
// })

Answer №1

An attempt to simplify this by using only one generic binding (J extends Job) and inferring T through a

Fulfilment<J extends Job<unknown>>
fails, as demonstrated in this code playground

https://i.sstatic.net/cMXcJ.png

However, the issue can be resolved by explicitly asserting the type with...

(await job()) as Awaited<ReturnType<typeof job>>

It may seem unexpected that Typescript requires this additional information, but it is necessary.

type Job<T> = () => Promise<T>;

export interface JobFulfilment<J extends Job<unknown>> {
  job: J;
  status: "fulfilled";
  value: Awaited<ReturnType<J>>;
}

export interface JobRejection<J extends Job<unknown>> {
  job: J;
  status: "rejected";
  reason: any;
}

export type JobSettlement<J extends Job<unknown>> =
  | JobFulfilment<J>
  | JobRejection<J>;

export async function* createSettlementSequence<J extends Job<unknown>>(
  createJobSequence: () => AsyncGenerator<J>,
): AsyncIterable<JobSettlement<J>> {
  for await (const job of createJobSequence()) {
    try {
      const value = (await job()) as Awaited<ReturnType<typeof job>>;
      yield {
        job,
        value,
        status: "fulfilled",
      };
    } catch (reason) {
      yield {
        job,
        reason,
        status: "rejected",
      };
    }
  }
}

const settlementSequence = createSettlementSequence(async function* () {
  yield Object.assign(async () => "foo", { task: 0 });
  yield Object.assign(async () => "bar", { task: 1 });
  yield Object.assign(async () => "baz", { task: 2 });
});

for await (const settlement of settlementSequence) {
  if (settlement.status === "fulfilled") {
    const { value, job } = settlement;
    console.log(`Task ${job.task} successfully returned ${value}`);
  }
}

Similar questions

If you have not found the answer to your question or you are interested in this topic, then look at other similar questions below or use the search

Is there a way to ensure DRY principles are followed while utilizing Redux Toolkit's asyncThunkCreator?

I have a query related to RTK. I find myself repeating code in my action creators created with createAsyncThunk because I want to be able to cancel any request made. I am thinking of creating a wrapper to streamline this process, but I am facing challenge ...

The method Office.context.mailbox.item.internetHeaders.setAsync has not been configured

I am integrating the Microsoft Office API into Outlook. I'm attempting to add an extra x-header to my email in the composer scope for later identification. To achieve this, I referred to the following documentation: https://learn.microsoft.com/en-us/j ...

Angular2 - Breaking down applications into reusable components

Utilizing custom properties permits seamless data binding between multiple components. <section id="main"> <app-home [dict]="dict">Hello there!</app-home> </section> In this scenario, dict serves ...

Empowering your Angular2 application with data binding

I am currently working with the following template: <table width="700"> <caption>All Users</caption> <thead> <tr> <th>name</th> <th>surname</th> < ...

When the state changes, the dialogue triggers an animation

Currently, I am utilizing Redux along with material-ui in my project. I have been experimenting with running a Dialog featuring <Slide direction="up"/> animation by leveraging the attribute called TransitionComponent. The state value emai ...

Error detected in Deno project's tsconfig.json file, spreading into other project files - yet code executes without issues?

I am working on a Deno project and need to utilize the ES2019 flatMap() method on an array. To do this, I have created a tsconfig.json file with the following configuration: { "compilerOptions": { "target": "es5", ...

How can I display an agm-polyline within a map in Angular 7?

I need assistance with adjusting the polylines on my map and dynamically setting the zoom level based on their size and position. Here is the code I am currently using: <agm-map style="height: 500px" [latitude]='selectedLatitude' [longitude ...

Executing TypeORM commands yields no output

It's been a while since I last tested my Nest project with TypeORM, and now when I try to run any TypeORM command, nothing happens. I attempted to run TypeORM using these two commands: ts-node -r tsconfig-paths/register ./node_modules/typeorm/cli.js ...

Display a custom error message containing a string in an Angular error alert

How can I extract a specific string from an error message? I'm trying to retrieve the phrase "Bad Request" from this particular error message "400 - Bad Request URL: put: Message: Http failure response for : 400 Bad Request Details: "Bad Request ...

Is it possible to utilize a TypeScript type in conjunction with io-ts?

Currently, I am in the process of validating API responses with io-ts. In my TypeScript setup, I have already defined the following data structure: export type Group = { id: number; name: string; } Now, my objective is to incorporate this type into ...

What steps are involved in setting up a Typescript-based custom Jest environment?

Currently, I am attempting to develop an extension based on jest-node-environment as a CustomTestEnvironment. However, I encountered an error when trying to execute jest: ● Test suite failed to run ~/git/my-application/tests/environment/custom-test ...

Using *ngFor to iterate through a nested collection in an Angular 2 application

I'm currently working on a challenge involving drilling down to iterate over an array within another collection of arrays within an Angular 2 application. To start off, I have set up my component to subscribe to an observable in the ngOnInit lifecycle ...

Problem with timing in token interceptor and authentication guard due to injected service

Currently, I am facing an issue where I need to retrieve URLs for the auth service hosted on AWS by reading a config.json file. In order to accomplish this, I created a config service that reads the config file and added it as a provider in app.module. Eve ...

Unable to trigger dispatchEvent on an input element for the Tab key in Angular 5

In my pursuit of a solution to move from one input to another on the press of the Enter key, I came across various posts suggesting custom directives. However, I prefer a solution that works without having to implement a directive on every component. My a ...

Is it acceptable to have an empty dependency array in React.useEffect?

Within my child React component, I receive an itemList prop from the parent component. This prop is an array of objects that contain data fetched from an endpoint. My goal in the child component is to enhance each object in the itemList array by adding mo ...

Utilizing getServerSideProps and getInitialProps in Next.js to optimize page loading times

My page is not loading when I use getServerSideProps or getInitialProps. It keeps on loading without displaying the content, but everything works fine when I remove them. What could be wrong with my code here? HELP. ... interface Props { data: any; } co ...

Discovering duplicates for properties within an array of objects in React.js and assigning a sequential number to that specific field

I am working with an array of objects where each object contains information like this: const myArr=[{name:"john",id:1}{name:"john",id:2}{name:"mary",id:3}] In the first 2 elements, the "name" property has duplicates with the value "john". How can I updat ...

"Alert in Javascript executing prematurely prior to initiating the function for sending a get request

private validateURL(url: string) { let isValid = false; this.$http.get(url).then( (data) => { console.log('success'); isValid = true; } ).catch( (reason) => { console. ...

Using Lodash to eliminate objects from a list

I have a specific model for my list, it looks like this: Animal Model id name age gender city Within the animals[] = []; array that I am working with, I need to remove the fields name, age, and gender while keeping id and city. How c ...

Enhancing TypeScript Types with a custom method within an Angular 2 context

I am working on an Angular 2 project using the CLI. Currently, I am trying to add a custom method to the String type as shown below. interface String { customMethod(): number; } String.prototype.customMethod = function() { return 0; } Despite my ...