【问题标题】:"Function expression, which lacks return-type annotation, implicitly has an 'any' return type" when adding void operation“函数表达式,缺少返回类型注释,隐式具有‘任何’返回类型”添加 void 操作时
【发布时间】:2022-01-10 08:59:20
【问题描述】:

我在 TypeScript 上遇到了一个奇怪的问题。我最近了解了void ... 运算符,因为我需要应用它,所以eslint 不会报告no-floating-promises。然而,这个特殊的 sn-p 不知何故导致了一个问题,我无法在 TypeScript 操场上重现

class A {

  async a() {}

  async onTickAsync(repeat: boolean) {
    try {
      await this.a();
    } catch(e) {
      console.error(e);
    } finally {
      if (repeat) {
        window.setTimeout(() => void this.onTickAsync(true), 200);
      }
    }
  }

}

VS Code 会报这个错误:

TS7011:缺少返回类型注释的函数表达式隐式具有“任意”返回类型。

但是,TS Playground 上无法重现该问题。 VS Code 和 Playground 都使用 TypeScript 4.5.4。这是我的tsconfig.json

{
    "compileOnSave": true,
    "compilerOptions": {
        "noImplicitAny": true,
        "noEmitOnError": true,
        "sourceMap": true,
        "target": "ESNext",
        "module": "ESNext"
    },
    "exclude": [
        "node_modules"
    ]
}

我知道可以通过添加: void返回类型或删除void操作或删除noImplicitAny来修复它:

window.setTimeout((): void => void this.onTickAsync(true), 200);

我想问:是什么导致了错误?为什么它只发生在我的 IDE/本地而不是操场上?


为了确保这不仅仅是因为 VS Code,我还在单独的终端上运行了 tsc --versiontsc

tsc --showConfig 输出:

PS C:\Users\lukev\Downloads\Temp> tsc --showConfig
{
    "compilerOptions": {
        "noImplicitAny": true,
        "noEmitOnError": true,
        "sourceMap": true,
        "target": "esnext",
        "module": "esnext"
    },
    "files": [
        "./test.ts"
    ],
    "exclude": [
        "node_modules"
    ],
    "compileOnSave": true
}

这也很有趣,它不会发生在其他功能上。例如,这不会产生任何错误。这似乎与window.setTimeout 有关。例如,我发现Function 类型和() => void 之间存在一些不同):

class A {

  doSomething1(_: Function) { }
  doSomething2(_: () => any) { }
  doSomething3(_: () => void) { }

  async a() { }

  async onTickAsync(repeat: boolean) {
    // Only this one produces error
    this.doSomething1(() => void this.onTickAsync(true));
    this.doSomething2(() => void this.onTickAsync(true));
    this.doSomething3(() => void this.onTickAsync(true));
  }

}

【问题讨论】:

  • @LukeVo 您会在问题中包含tsc --showConfig 的输出吗?
  • @jsejcksn 不知道该命令。已添加。
  • @T.J.Crowder 和你的截图一模一样。 4.5.4 — TypeScript version。另外我认为这不是由于 VS Code,因为它也发生在独立终端上。
  • 嗯,这似乎真的很确定。 :-)(我误解了你之前关于单独运行tsc 的评论;我现在明白了。)谢谢!
  • 我发现它与 setTimeout 使用 TimerHandler = string | Function 作为其参数类型有关。 Playground 和我的 lib.dom.d.ts 之间可能有什么不同吗?

标签: typescript function void any


【解决方案1】:

权威答案请见microsoft/TypeScript#36651

您的问题是您已启用the --noImplicitAny compiler option,但您启用the --strictNullChecks compiler option。您可以在 TypeScript Playground and reproduce your issue 中设置这些选项。

另外:请注意--strictNullChecksthe --strict family of compiler features 的一部分,通常建议将其作为事实上的“标准”类型安全级别的一部分。您并没有真正询问您应该使用哪些编译器选项,但请注意,如果您使用一组不常见的编译器选项,您更有可能遇到一般 TypeScript 社区不为人知的编译器行为。好了,差不多了。

所以我们知道如何重现,但没有明确回答为什么这里有错误。现在让我们这样做。启用--strictNullChecks 后,the void opterator 会生成the undefined type 的值。但是使用--strictNullChecks disabled,实际上并没有明显的undefined 类型,void 运算符产生the any type 的值。除非你明确annotate 类型是any,否则--noImplicitAny 下会出现错误:

// with --strictNullChecks disabled

() => void 0; // error!
// Function expression, which lacks return-type annotation, 
// implicitly has an 'any' return type.

(): undefined => void 0; // okay
//^^^^^^^^^^^ <-- arrow function return type annotation

如您所见,如果void 运算符的返回类型为contextual type,您也可以摆脱错误:

function foo(cb: () => any) { }
foo(() => void 0); // okay, void 0 is contextually typed as any

注意Function 接口有点奇怪,没有真正的call signature,见microsoft/TypeScript#20007,所以它不能提供上下文类型:

function bar(cb: Function) { }
bar(() => void 0); // implicit any error!

Playground link to code

【讨论】:

  • 太棒了,谢谢。旁注实际上非常有用,我不知道这一点。我一定会在我未来的项目中使用该标志。
【解决方案2】:

我可以重现您的问题,虽然我无法回答为什么会发生这种情况,但您可以通过省略 void operator 并将 catch 方法链接到承诺满足 ESLint:

// before
window.setTimeout(() => void this.onTickAsync(true), 200);

// after
window.setTimeout(() => this.onTickAsync(true).catch(() => {}), 200);

【讨论】:

  • 我不是反对者,但我确实在我的问题中解释了我知道如何解决它。此外,有时删除它实际上很危险,因为函数返回的值可能会在未来改变回调行为(参见description of void operator)。在这种情况下,添加显式 : void 返回类型更有利:)
  • @LukeVo 我只是提供了另一个你没有提到的同时满足编译器和 ESLint 的建议。
  • 抱歉,我没注意到。我添加了一个赞成票,尽管我认为简单地添加 : void 仍然更简单(并且仍然有 void 运算符),如果我有 void 运算符,我不需要 catch
  • @LukeVo 不用担心。我的建议还实际上防止了未处理的承诺拒绝(这是您使用的 ESLint 规则试图帮助您防止的)。
  • @jsejcksn - OP 的 onTickAsync 永远不会拒绝它的承诺(除非 window.setTimeout 抛出错误,我认为我们可以假设它永远不会这样做)。
猜你喜欢
  • 2021-05-18
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2015-03-24
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多