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

11
在TypeScript(3.6.3)中,Generator<>几乎与IterableIterator<>相同。当Generator<>扩展Iterator<>时,默认第三个泛型参数(TNext)unknownIterator<>本身将TNext默认为undefined。因此,GeneratorIterator(以及IterableIterator)不太匹配,可能会出现问题。
let gen2:IterableIterator<string>;

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

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

我是否漏掉了什么?这是有好的原因吗?


1
我不会感到惊讶,如果在TypeScript的github问题中有一个线程隐藏着,可以解释这种差异。我自己不知道原因,但我想指出,如果你删除: Generator<string>并让TypeScript自己推断返回类型,那么它就是Generator<string, void, undefined> - Patrick Roberts
1个回答

18

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

为什么要添加它们?

(在这个提交中) 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

在这里,我们可以看到问题——我们不能确定所有逻辑规则是否已经返回或产生了值。因此他们引入了TReturn。TNext被引入:

[...] 根据生成器的返回类型注释(即上面“Generator”中的TNext类型),正确检查并提供yield表达式的结果类型。

为什么要使用默认值?

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

我们必须注意,在生成器和非生成器迭代器中使用next()函数存在惯用差异。如ECMA-262备注所述为迭代器。

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

迭代器主要用于for-of循环,它们不会向next传递参数。事实上,向next函数传递参数非常罕见(MDN甚至将其称为“零参数函数”)。因此,TNext默认值的唯一明智选项将是undefined。让它变成unknown将极大地阻碍类型检查(更不用说使用--strictNullChecks编译的代码了)。

如果使用生成器向next()函数传递参数不是一个相当常见的做法——它实际上有一个有效的用例……并在标准中定义了行为

Generator.prototype.next(value)

next方法执行以下步骤:

  1. g成为this值。
  2. 返回?GeneratorResume(g, value, empty)。

而在MDN中:

要发送给生成器的值。

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

更不用说,在典型的用例中,第一个.next()调用将在没有参数的情况下调用,随后的调用将带有参数。不幸的是,没有办法指定“可选的第一次,未知的后续时间”类型,因此我想鉴于所有这些情况,他们对生成器中的TNext选择了unknown。

当然没有完美的情况。但他们必须为他们认为最少问题的解决方案做出选择。

所有这些问题都在此问题中讨论,供所有感兴趣的人参考。


网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接