为什么 TypeScript 的 IterableIterator<> 和 Generator<> 泛型略有不同?

Pre*_*age 6 iterator generator typescript

在 TypeScript (3.6.3) Generator<>中几乎与IterableIterator<>相同。当Generator<>扩展Iterator<> 时,它默认第三个泛型参数(TNext)unknownIterator<>本身默认TNextundefined。所以GeneratorIterator(以及IterableIterator)并没有像他们应该的那样排列。

let gen2:IterableIterator<string>;

function* gen1():Generator<string> {
    yield* gen2;
}
Run Code Online (Sandbox Code Playgroud)

yield* 行是一个错误:“无法将迭代委托给 value,因为其迭代器的 'next' 方法期望类型为 'undefined',但包含的生成器将始终发送 'unknown'.ts(2766)”。

我错过了什么吗?这有充分的理由吗?

dom*_*ndo 10

这实际上是一个非常复杂的问题。我不会假装完全理解它。但也许我可以提供一些见解。

他们为什么要添加它们?

(在此提交中)。Typescript 决定,无论好坏,它都希望对生成器进行更严格的类型检查。这实际上有一些正当的理由。看下面的例子

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

let gen = foo(),
    curr;

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

// At his point we should know that 
// curr.value is a string because curr.done is true
Run Code Online (Sandbox Code Playgroud)

在这里我们可以看到问题——我们不知道一个值是否被我们应该的所有逻辑规则返回或产生。所以他们推出了TReturn。TNext介绍

[…]yield根据生成器返回类型注释的下一个类型(即上面定义中的TNext类型)正确检查表达式的结果并为其提供类型Generator

为什么是默认值?

现在,如果您决定进行此类更改,您可能会破坏一些代码——目标是尽可能少地破坏。

我们必须注意,next()在生成器和非生成器迭代器中函数的使用存在惯用差异。正如ECMA-262对迭代器的评论

参数可以传递给next函数,但它们的解释和有效性取决于目标迭代器。for-of 语句和 Iterator 的其他常见用户不传递任何参数,因此期望以这种方式使用的 Iterator 对象必须准备好处理被无参数调用的情况。

迭代器主要用于不向 next 传递参数的 for-of 循​​环。实际上,将参数传递给下一个函数的情况非常罕见(MDN 甚至称其为“零参数函数”)。因此,默认值的唯一合理选项TNextundefined。让它unknown成为类型检查的一大障碍(更不用说用 编译的代码了--strictNullChecks)。

如果next()使用生成器将参数传递给函数不是一种非常普遍的做法,那一切都会很棒——它实际上有一个有效的用例……并且在标准中定义了行为:

Generator.prototype.next()

next方法执行以下步骤:

  1. gthis值。
  2. 返回 ?GeneratorResume( g , value , empty)。

MDN 中

要发送到生成器的值。

该值将作为yield表达式的结果进行分配。例如,在 中variable = yield expression,传递给.next()函数的值将被分配给variable

更不用说,在典型的用例中,第一个.next()调用将在没有参数的情况下被调用,随后的调用将被调用。不幸的是,没有办法指定“可选的第一次,未知的后续时间”类型,所以我猜鉴于他们unknownTNextin Generators解决了所有这些问题。

当然,没有完美的情况。但他们必须接受他们认为最不成问题的事情。

所有这些问题都在本期中为任何感兴趣的人进行了讨论。