打字稿断言式防护

时间:2018-03-19 12:14:26

标签: typescript

如果if的函数调用返回never类似于undefined的函数调用,那么这种情况是否可以限制assert

示例代码:

interface Foo { bar(): void }
function getFoo(): Foo | undefined { }

function test() {
    const foo = someService.getFoo();
    assert(foo);
    if (!foo) { // now mandatory because without this foo may be still undefined even if assert protects us from this
        return;
    }
    foo.bar(); // , here foo may be undefined
}

我希望能够以这样的方式编写assert,以便我可以跳过if (!foo)子句并将foo类型限制为普通Foo

这在打字稿中是否可行?

我尝试为never添加带有抛出的类型的重载:

function assertGuard(v: undefined | null | '' | 0 | false): never;
function assertGuard(v: any): void; // i'm not sure which one is  captured by TS typesystem here

function assertGuard<T>(v: T | undefined) {
    if (v === undefined || v === null || v === '' || v === 0 || v === false) {
         throw new AssertionError({message: 'foo'})
    }
}

这个编译,但是对assertGuard(foo)的调用无法识别undefined,它会返回never,因此不会将foo限制为Foo

我找到了可能的解决方法,但我认为经典的assert是一种更清洁的方法:

function assertResultDefined<T>(v: T|undefined): T | never {
    if (v === undefined) {
        throw new Error('foo');
    }
    return v;
}
function die(): never { throw new Error('value expected)}

const foo = assertResultDefined(getFoo()) // foo is Foo, undefined is erased
const foo = getFoo() || die();
    // undefined is erased from foo
    / CONS: doesn't play well with types that interpolate to `false` like 0, ''

4 个答案:

答案 0 :(得分:3)

https://github.com/Microsoft/TypeScript/issues/8655的打字稿积压存在问题。所以现在你不能这样做。

您可以做的是使用“非空断言运算符”。哪个强制打字稿放松非空检查。使用这种情况,你绝对确定它不会导致空引用。

function test() {
     const foo = getFoo();
     assert(foo);
     foo!.bar(); //Dang it typescript! I know better.
}

答案 1 :(得分:3)

Typescript 3.7添加了assertions in control flow analysis

  

返回asserts的谓词表示该函数仅在断言成立时返回,否则抛出异常

消费者方面不再需要花费金钱。

interface Foo { bar(): void }
declare function getFoo(): Foo | undefined;

function assert(value: unknown): asserts value {
    if (value === undefined) {
        throw new Error('value must be defined');
    }
}

function test() {
    const foo = getFoo();
    // foo is Foo | undefined here
    assert(foo);
    // foo narrowed to Foo
    foo.bar();
}

Playground

答案 2 :(得分:1)

由于fooFoo | undefined,其类型应以某种方式更改为Foo

在上面的代码中,这可以合理地完成:

let foo = getFoo(); // Foo | undefined
foo = assertResultDefined(foo); // Foo
foo.bar();

另一种选择是使用非空断言(如另一个答案所示):

let foo = getFoo();
foo = assertResultDefined(foo);
foo = foo!;
foo.bar();

答案 3 :(得分:1)

这应该对您有用:

const foo = (a: number | null) => {
  a = shouldBe(_.isNumber, a)
  a  // TADA! a: number
}

const shouldBe = <T>(fn: (t1) => t1 is T, t) => (fn(t) ? t : throwError(fn, t))

const throwError = (fn:Function, t) => {
  throw new Error(`not valid, ${fn.name} failed on ${t}`)
}

其中_.isNumber具有类型保护x is number 可以与带有类型保护的任何功能一起使用。

关键是您必须重新分配变量,因此assert实际上是一个身份函数,会在失败的类型断言时引发错误