我可以编写一个抛出异常而不是返回布尔值的类型保护吗?

Can I write a type guard that throws exceptions instead of returning booleans?

我有一个 class 在多个函数中使用相同类型的守卫;像这样:

function validData(d: Data | null): d is Data {
    return d !== null;
}

class C {
    data: Data | null;

    public doA() {
        if (!validData(this.data))
            throw new Error("Invalid data");
        /* … */
    }

    public doB() {
        if (!validData(this.data))
            throw new Error("Invalid data");
        /* … */
    }
}

我能否重构此代码以将错误移至类型保护中?像这样:

function assertData(d: Data | null): ??? {
    if (d === null)
        throw new Error("Invalid data");
}

…我可以这样使用:

class C {
    data: Data | null;

    public doA() {
        assertData(this.data);
        /* … */
    }

    public doB() {
        assertData(this.data);
        /* … */
    }
}

目前我正在使用以下解决方法:

function must(d: Data | null): Data {
    if (d === null)
        throw new Error("Invalid data");
    return d;
}

…但这迫使我将对 this.data 的所有访问都包装在 must().

不是您想要的,但您可以添加一个 is annotation 以使用具有 if 条件的函数:

type Data = string;
function assertData(d: Data | null): d is Data {
  if (d == null)
    throw new Error("Invalid data");
  return true;
}
// Use
let foo: Data | null = null;
if (assertData(foo)) {
  foo.toUpperCase(); // inferred to be Data
}

编辑 自最初的答案以来,typescript 在 this PR

中添加了自定义类型断言的能力
type Data = { foo: string };

function assertData(d: Data | null): asserts d is Data {
    if (d == null)
        throw new Error("Invalid data");
}
// Use
declare var bar: Data | null;
bar.foo // error as expected
assertData(bar)
bar.foo // inferred to be Data

Playground Link

原回答

不幸的是,类型保护的当前语法需要 if 语句才能工作。所以这有效

type Data = { foo: string };
function assertData(d: Data | null): d is Data {
    if (d == null)
        throw new Error("Invalid data");
    return true;
}
// Use
let bar: Data | null = null;
if (assertData(bar)) {
    bar.foo // inferred to be Data
}

但是没有办法让它工作:

let bar: Data | null = null;
assertData(bar);
bar.foo // bar will still be Data | null

正如其他人已经指出的那样,没有语法方法可以通过类型保护来实现这一点。我认为按照 TypeScript 开发人员的意图解决此类问题的方法是使用新变量或重新分配缩小类型:

function assertData(d: Data | null): Data {
  if (d === null)
    throw new Error("Invalid data");
  return d;
}

class C {
  data: Data | null;

  public doA() {
    const data = assertData(this.data);
    /* do what you want with data instead of this.data */
  }
}

或者,您可以使用 this.data 进行重新分配,这应该足以缩小后续代码中的类型:

class C {
  data: Data | null;

  public doA() {
    this.data = assertData(this.data);
    /* … */
  }
}

这还具有比类型保护更类型安全的优势,因为类型保护允许您 "lie" 关于变量的类型,而这在这里是不可能的。

你可以这样做,这是最方便(目前可能)的方式。

function validData(d: Data | null): d is Data {
  if (d === null) {
    throw new Error("Invalid data");
  }

  return d !== null;
}

class C {
  data: Data | null;

  public doA() {
    if (!validData(this.data)) return;

    /* typeof this.data equals Data now */
    /* … */
  }

  public doB() {
    if (!validData(this.data)) return;

    /* typeof this.data equals Data now */
    /* … */
  }
}