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>>

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

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

Strategies for addressing the issue of assigning "xx" to intrinsic attributes and props in React with TypeScript

I'm facing an issue where I am unable to locate 'count' and assign {count: number; title:string} type to IntrinsicAttributes in a React and TypeScript environment. There are two components involved, ParentComponent and ChildComponent. In t ...

Customize Angular Material's Mat-Dialog background blur/darkening effect

Greetings, dear community members, I am currently utilizing angular along with angular material in my projects. By default, when a material dialog is opened, it slightly darkens the background. However, I am interested in having a blurred background inst ...

Error code 70006 encountered in Typescript when attempting to reference a type as "any"

Currently, I am working on a React project that involves using TypeScript. This is quite new to me, and I have encountered a type error in one of my components... let dragStart = (e) => { let transferringData = e.dataTransfer.setData("text", e.tar ...

Wrapping an anonymous function in a wrapper function in Typescript can prevent the inferred typing

I am encountering an issue with typing while coding: function identity<T>(v: T): T{ return v; } function execute(fn: {(n: number):string}) {} execute((n) => { // type of n is 'number' return n.toFixed(); }) execute(identity(( ...

Angular 2 partial static routing parameters with customizable features

Can an angular 2 routing configuration include a partial-static parameter? Currently, I am using a classic parameter setup like this: const routes: Routes = [ { path: ':type/fine.html', pathMatch: 'full', redirectTo: &ap ...

What is causing the issue where search query parameters are not recognizing the initially selected option?

Hey, I'm having an issue with searchParams. The problem is that when I apply filters like "Breakfast, Lunch, Dinner", the first chosen option isn't showing up in the URL bar. For example, if I choose breakfast nothing happens, but if I choose lun ...

Enforcing alias types in TypeScript arguments is necessary for maintaining consistency and clarity

I'm currently facing a challenge with type unions and aliases. I have an alias for values that can possibly be null or undefined, along with a function that handles these values. Everything is running smoothly and safely. However, there are instances ...

I'm looking to find the Angular version of "event.target.value" - can you help me out?

https://stackblitz.com/edit/angular-ivy-s2ujmr?file=src/app/pages/home/home.component.html I am currently working on getting the dropdown menu to function properly for filtering the flags displayed below it. My initial thought was to replicate the search ...

Why isn't my component calling the service.ts file?

In my Angular project, I am working on a home component that contains a specific method. This method is defined in a service file called service.ts and imported into the homecomponent.ts file. The method is named filterClicked with a condition within the s ...

The process of implementing ngOninit with asynchronous data involves handling data that may take

Within the ngOnInit method, I am calling a service method and assigning the return value to a member variable. However, when trying to access this variable later in the ngOnInit again, it seems that due to synchronization issues, the value has not been ass ...

I'm in need of someone who can listen and detect any changes in my notifications table (node) in order to perform real-time data

Seeking a listener in Firebase to track changes in my notifications table for real-time data monitoring. My project is utilizing Angular 2 with TypeScript and Firebase. ...

I'm looking to configure @types for a third-party React JavaScript module in order to use it with TypeScript and bundle it with webpack. How can I accomplish this?

Imagine you have a third-party npm package called @foo that is all Javascript and has a module named bar. Within your TypeScript .tsx file, you want to use the React component @foo/bar/X. However, when you attempt to import X from '@foo/bar/X', y ...

The search is on for the elusive object that Angular 2/4

Why am I encountering the error message saying "Cannot find name 'object'"? The error message is: Error: core.service.ts (19,23): Cannot find name 'object'. This error occurs in the following line of code: userChange: Subject<ob ...

"Error encountered while executing a code snippet using Navalia in TypeScript

I have been attempting to execute this code snippet from https://github.com/joelgriffith/navalia but despite my efforts, I have not been able to get it running smoothly without encountering errors: navaliatest.ts /// <reference path="typings.d.ts" /&g ...

Absence of "Go to Definition" option in the VSCode menu

I'm currently working on a Typescript/Javascript project in VSCODE. Previously, I could hover my mouse over a method to see its function definition and use `cmd + click` to go to the definition. However, for some unknown reason, the "Go to Definition" ...

Creating a regular expression for validating phone numbers with a designated country code

Trying to create a regular expression for a specific country code and phone number format. const regexCountryCode = new RegExp('^\\+(48)[0-9]{9}$'); console.log( regexCountryCode.test(String(+48124223232)) ); My goal is to va ...

How can I call a method from a class using Typescript when getting an error saying that the property does not exist on the

Below is a service definition: export class MyService { doSomething(callbacks: { onSuccess: (data: Object) => any, onError: (err: any) => any }) { // Function performs an action } } This service is utilized in a component as shown be ...

Guide on accessing a modal component in Angular?

I have an Edit Button on my component called SearchComponent. When the user clicks this button, it currently redirects them to another component named EditFormComponent using navigateByUrl('url-link'). However, I would like to enhance the user ex ...

What is the approach to forming a Promise in TypeScript by employing a union type?

Thank you in advance for your help. I am new to TypeScript and I have encountered an issue with a piece of code. I am attempting to wrap a union type into a Promise and return it, but I am unsure how to do it correctly. export interface Bar { foo: number ...

Performing actions simultaneously with Angular 2 directives

My custom directive is designed to prevent a double click on the submit button: import { Directive, Component, OnInit, AfterViewInit, OnChanges, SimpleChanges, HostListener, ElementRef, Input, HostBinding } from '@angular/core'; @Directive({ ...