What is the reason behind the slight difference between TypeScript's IterableIterator<> and Generator<> generics?

In TypeScript version 3.6.3, there is a notable similarity between Generator<> and IterableIterator<>. However, when Generator<> extends Iterator<>, the third generic argument (TNext) defaults to unknown. On the other hand, Iterator<> defaults TNext to undefined. This discrepancy means that Generator and Iterator (and IterableIterator) do not align as smoothly as one might expect.

let gen2: IterableIterator<string>;

function* gen1(): Generator<string> {
    yield* gen2;
}

The line with 'yield*' produces an error stating: "Cannot delegate iteration to value because the 'next' method of its iterator expects type 'undefined', but the containing generator will always send 'unknown'. ts(2766)".

Is something missing here? Is there a valid reason for this behavior?

Answer №1

This issue at hand is quite complex, and I must admit that I do not fully grasp it. However, I may be able to shed some light on the matter.

What prompted these additions?

In a particular commit (referenced here), TypeScript made the decision to enforce stricter type checks for generators. This change was implemented with valid reasoning, as exemplified in the following scenario:

function* foo() {
    let m = 0;
  
    while (m < 10) {
      yield m++;
    }
    
    return "done";
}

let gen = foo(),
    curr;

while(!(curr = gen.next()).done) {}

// At this point we should deduce 
// that curr.value is a string because curr.done is true

The challenge lies in discerning whether a value has been returned or yielded—despite logical expectations dictating otherwise. Hence, the introduction of TReturn and TNext was deemed necessary, as elucidated in their implementation here:

[…] correctly check, and provide a type for, the result of a yield expression based on the next type of the generator's return type annotation (i.e. the TNext type in the Generator definition above).

Why incorporate default values?

If a decision is made to enact such alterations, it is inevitable that some existing code will be affected—the aim being minimal disruption. It is crucial to note that there exists contrast in the usage of the next() function between generators and non-generator iterators, as stipulated in the ECMA-262 documentation.

While arguments may be passed to the next function, their interpretation and validity hinge upon the target Iterator. The common utilization of Iterators, such as in for-of loops, does not involve passing any arguments; hence, Iterator objects anticipating such usage must accommodate calls without arguments.

Generators are predominantly employed in for-of loops where no argument is supplied to next. In fact, the act of furnishing an argument to the next function is exceedingly rare (as underscored by MDN referring to it as a "zero-argument function"). Therefore, setting the default value of TNext to 'undefined' is the most pragmatic choice—one that avoids impeding type checking complexities, particularly when compiled with '--strictNullChecks'.

All seems well until one considers scenarios where passing an argument to the `next()` function with generators is commonplace—a practice validated in the standard specifications:

Generator.prototype.next(value)

The next method executes the following steps:

  1. Let g represent the current instance.
  2. Return ?GeneratorResume(g, value, empty).

In accordance with MDN documentation:

The provided value serves as input to the generator.

Said value is allocated as the outcome of a yield expression. For example, given `variable = yield expression`, the value passed to `.next()` will be assigned to `variable`.

Moreover, typically, the initial `.next()` call lacks an argument, whereas subsequent calls include one. Regrettably, demarcating a type as "optional first time, unknown subsequently" proves challenging; thus, opting for 'unknown' as the TNext default within Generators appeared reasonable given the circumstances.

Inevitably, achieving perfection remains elusive. Thus, compromises reflecting lesser consequences are sought after.

For those intrigued by the intricacies discussed herein, further insight can be gleaned from this documented issue.

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

Create a const assertion to combine all keys from an object into a union type

I am working with an object similar to this (demo link): const locations = { city: {name: 'New York'}, country: {name: 'United States'}, continent: {name: 'North America'} } as const My goal is to create a union t ...

Typescript encounters transpilation issues when the spread operator is omitted for undefined values {...undefined}

I am currently working on a TypeScript project where I have encountered a peculiar issue. Within some of my TypeScript files, I am including a plain JavaScript/Node file named config.js. The content of config.js is as follows: 'use strict'; modu ...

Strategies for passing multiple props to a function in React using TypeScript, such as useState([]) and useState(boolean)

Dealing with API Structure { "default": [ { "id": 1, "name": "A" }, { "id": 2, "name": "B" }, { "id": 3, "name" ...

What is the process for connecting a date/time form control?

My code seems to only be working for the 'title' element, while the 'docdatetime' control remains blank. Can anyone spot what I'm doing wrong? //template =================================================== <div class="form-grou ...

The package.json entry for "abc-domains" is not functioning correctly even though it is set to "latest" version

Unique Scenario Imagine there's a package called xyz-modules that I've developed. The package.json file in my project looks like this: ... "devDependencies": { "@company/xyz-modules": "latest", ... } ... After ...

What is the recommended way to handle data upon retrieval from a Trino database?

My goal is to retrieve data from a Trino database. Upon sending my initial query to the database, I receive a NextURI. Subsequently, in a while loop, I check the NextURI to obtain portions of the data until the Trino connection completes sending the entire ...

The intended 'this' keyword is unfortunately replaced by an incorrect '

Whenever the this keywords are used inside the onScroll function, they seem to represent the wrong context. Inside the function, it refers to the window, which is understandable. I was attempting to use the => arrow notation to maintain the correct refe ...

Adding a backslash in Angular: Tips and Tricks

I have a question about adding a backslash to a string that is returned from a div, for example Car1 \sold. Although I am able to retrieve the status, I am having trouble adding the backslash. Here is what I have tried so far: <span>{{addBackl ...

Issue encountered while retrieving the response, in case the node.js server sends the response with a delay

My aim is to upload an image and have the nodeJS server send the path of that image folder back as a response. Unfortunately, when I try sending the response after completing a task, nothing seems to be happening on the angular-side. Below is my componen ...

Merging Promises in Typescript

In summary, my question is whether using a union type inside and outside of generics creates a different type. As I develop an API server with Express and TypeScript, I have created a wrapper function to handle the return type formation. This wrapper fun ...

Uncover the mystery behind the return value of a generic function in TypeScript

I can't seem to wrap my head around why TypeScript is behaving in the way described below. Snippet 01| const dictionary: { [key: string]: unknown} = {} 02| 03| function set<T>(key: string, value: T): void { 04| dictionary[key] = value; 05| } ...

Ways to simulate a variable imported in the module being tested without it being a function parameter can be achieved by using describe.each and changing the mock value for each test

I have a requirement to test a function within my TypeScript module. module-to-test.ts import { config } from './app-config'; export const isSomethingWhatINeedSelector = createSelector( firstDependencySelector, secondDependencySelector ...

The InMemoryCache feature of Apollo quietly discards data associated with fragments that are declared on the main

After sending the following query to my GraphQL server: fragment B on root_query { foo { id } } query A { ...B } The data received from the server includes the foo field. However, when I retrieve it using Apollo's InMemoryCache a ...

Error: No provider found for _HttpClient in the NullInjector context

Hello everyone, I am new to Angular and currently facing an issue that has me stuck. The error message I'm receiving is as follows: ERROR NullInjectorError: R3InjectorError(Standalone[_AppComponent])[_ApiCallServiceService -> _ApiCallServiceService ...

What are the best practices for preventing risky assignments between Ref<string> and Ref<string | undefined>?

Is there a way in Typescript to prevent assigning a Ref<string> to Ref<string | undefined> when using Vue's ref function to create typed Ref objects? Example When trying to assign undefined to a Ref<string>, an error is expected: co ...

Grunt is your go-to resource for instructions on executing these tasks before the main program

Before launching my app, I need to make sure a specific grunt task is executed first: node app.js I'm having trouble finding information on how to automatically run and complete a Grunt task before initiating a node command. In particular, I have T ...

Struggling to incorporate generics into a Typescript method without sacrificing the typing of object keys

Currently, I am working on a method in Typescript that is responsible for extracting allowable property types from an object of a constrained generic type. The scenario involves a type called ParticipantBase which consists of properties like first: string ...

Using TypeScript to transform types: Array of objects with keys Kn and values Vn to an object with keys Kn and values Vn

I am looking to create a function that can process tuples with a specific structure like so: type Input = [ { key: K1, value: V1 }, { key: K2, value: V2 }, { key: K3, value: V3 }, // ... { key: KN, value: VN } ] The function should then output ...

Having trouble getting @types/express-session to function properly. Any suggestions on how to fix it?

My web-app backend is built with TypeScript and I've integrated express-session. Despite having @types/express and @types/express-session, I continue to encounter type errors that say: Property 'session' does not exist on type 'Request ...

Converting a promise of type <any> to a promise of type <entity>: A beginner's guide

As a newcomer to TypeScript and NestJS, I am wondering how to convert Promise<any[]> to Promise<MyEntity[]> in order to successfully execute the following code: const usersfromTransaction = this.repoTransaction .createQueryBuilder() ...