记一次关于extends的bug

记录一次关于用typescript封装restful api碰到的问题

这是我在 learn_koa 里面遇到的一个问题.

场景复现:

我需要封装Error到一个基类上面,然后后续所有自定义的异常全部继承这个基类,以后处理业务的时候就可以争对性处理。以下是js 的实现。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
class BaseError extends Error {
errorCode = 0

construct(errorCode = 0) {
super()
if (errorCode) {
this.errorCode = errorCode;
}
}
}

class CustomError extends BaseError {
errorCode = 100001

construct(errorCode = 0) {
super()
if (errorCode) {
this.errorCode = errorCode;
}
}
}

function start () {
try {
const success = Math.random() > 0.5;
if (success) {
throw new CustomError();
} else {
throw new Error();
}
} catch (e) {
if (e instanceof BaseError) {
// 出现了一个已知的错误
console.log('出错了!')
} else {
// 出现一个未知的错误
console.log('出错了!!!!')
// 通知维护人员,处理业务出现未知错误
}
}
}

start();

如上,我用了一个随机数来模拟处理业务出错的情况,在碰到已知的错误时我们可以用已有的逻辑来处理,比如参数校验错误了,直接通知前端错误,请求第三方出错了,就通知系统的维护人员。本质上的核心代码就是这一句:

1
(new CustomError()) instanceof BaseError; // true

在这里面的示例是可以返回true的,但是我在迁移到ts的时候。发现:

1
(new CustomError()) instanceof BaseError; // false

你没有看错,是false,当然这只在tsconfig 设置 compilerOptions 打包至es5的情况下,es6不存在以上的问题,但是目前大部分项目还是要降级到es5。

那么,这是为什么呢?我们可以通过两个原型链来看看。

1
console.log((new CustomError()).__proto__) // Error {}

如此,我们可以清楚的看到我们自定义的对象实例的本质是Error对象,并不是BaseError, 所以我们使用instanceof的时候是false。

那,有什么办法可以改变实例后的原型链么?

其实TS官方已经给出了答案,只需要在构造函数下加入下面一句就可以了

1
Object.setPrototypeOf(this, new.target.prototype);

完整实例如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45

class BaseError extends Error {
errorCode: number = 0

construct(errorCode: number = 0) {
super()
Object.setPrototypeOf(this, new.target.prototype);
if (errorCode) {
this.errorCode = errorCode;
}
}
}

class CustomError extends BaseError {
errorCode: number = 100001

construct(errorCode: number = 0) {
super()
if (errorCode) {
this.errorCode = errorCode;
}
}
}

function start () {
try {
const success = Math.random() > 0.5;
if (success) {
throw new CustomError();
} else {
throw new Error();
}
} catch (e) {
if (e instanceof BaseError) {
// 出现了一个已知的错误
console.log('出错了!')
} else {
// 出现一个未知的错误
console.log('出错了!!!!')
// 通知维护人员,处理业务出现未知错误
}
}
}

start();

我曾经被这个问题困扰了很久,于是把这个记录下来,以方便后续查看。