前端杂记
前端杂记
gy1 并发、异步与事件循环
1.1 嵌套的异步函数调用栈显示问题
1 |
|
这里的super.query(key)
也是一个异步函数,返回值为Promise<boolean>
。
在这两个函数中,第一个函数使用了await
关键字来等待super.query(key)
函数的返回值,而第二个函数则直接调用了super.query(key)
。上述两个函数的执行逻辑表现一致,且返回值一致。但是在抛出异常时会有一些区别,主要是在调用栈的显示上。
如果super.query(key)
函数抛出异常:
对于第一种写法,异常会被
hasData
捕获。在控制台显示的调用栈中,hasData
会被包含在内。对于第二种写法,异常将直接抛出到
hasData
上层。在控制台显示的调用栈中,hasData
不会被包含在内。
换言之,对于第二种写法,会导致调试信息中的调用栈显示不完整,不利于排查错误。建议在项目中采用第一种写法。
1.2 异步函数锁
JavaScript是单线程的,看似不需要引入锁机制来避免资源竞争,但是其异步特性也会导致资源被竞争操作。在具体的JavaScript项目中,如果一个比较复杂且耗时的异步函数,在同一时间被多次调用,它们之间可能会出现数据或者逻辑上的冲突。所以,我们还是有必要利用js的提供的特性来创建锁。
由于原生js并未提供核心库级别的锁(或者操作系统级别的锁),为此,我专门实现了一个Lock类,来保护那些容易受到资源竞争影响的函数。采用 Promise
+队列 的加锁方案。
1 |
|
具体使用示例:
1 |
|
此外,如果对锁的操作不当,可能导致死锁出现(即await opLock.lock()
永远处于pending态,函数执行被永久阻塞),以下操作可以避免死锁的出现:
- 如果被保护的函数存在多个
return
语句,那么请务必确保在每个return
之前调用unlock()
,避免遗漏。 - 若函数A和B均由同一个
Lock
实例进行保护,且函数A调用了函数B,那么此时会引起死锁,函数B的执行会被永远阻塞。建议不同层级的函数采用不同的锁实例来保护。 - 尽量避免在被保护的函数里抛出异常,因为这么做会终止当前函数执行,导致控制流跳过函数末尾的
unlock()
,无法释放锁。
2 工具库
2.1 Buffer的无效字符串编码问题
If
encoding
is'utf8'
and a byte sequence in the input is not valid UTF-8, then each invalid byte is replaced with the replacement characterU+FFFD
.
在Buffer库提供的buf.toString([encoding[, start[, end]]])
方法中,如果 encoding
是 'utf8'
并且输入中的字节序列不是有效的 UTF-8,则每个无效字节都会被替换为替换字符 U+FFFD
。
这个替换过程是静默的,不会有任何的提示。如果不想自己的二进制数据被篡改,请注意这个可能存在的静默替换过程。
3 语法特性
3.1 interface的条件可选属性
需求:在一个typescript项目中,我定义了下面这个接口:
1 |
|
其中,a
、b
、c
、d
、e
都是可选的属性,并且它们的可选性互不影响。但是我觉得这个interface所携带的类型约束信息有点少,因为现在有这么一个需求:如果用户实现了a
属性之后,b
和c
也必须被实现,但是d
和e
仍然保持原来的、互不影响的可选状态。要怎样修改上述代码来达到需求?
解答:一种解决方案是采用联合类型和交叉类型来增加这样的条件依赖。这个方法比较简单易懂,但是需要定义额外的interface来辅助,下面是实现代码:
1 |
|
上述代码创建了三个interface和一个type,ABC
必须同时具有 a、b 和 c, OptionalDE
包含可选的 d 和 e,MyInterface
定义为没有 a、b、c 属性,但包含可选的 d 和 e。最终的 NewMyInterface
是一个联合类型,也是我们最终要得到的结果。
当创建一个类型为NewMyInterface
的对象时,如果仅提供了a,没有提供b和c,编译器就会报错。同样,如果只提供了b而没有a和c,也会让编译器报错。
常见的bug诱因
下面介绍了一些虽然众所周知,却常常被不小心遗忘的bug诱因。这些也是我在实际项目中遇到过的:
1 git文件名不区分大小写
在git中,文件和文件夹的名称不区分大小写,git会自动忽略大小写的差异。比如在git库中有一个文件名为example.txt,那么无论是example.txt、EXAMPLE.TXT还是eXaMpLe.TxT都会被视为同一个文件。
2 调用无返回值的异步函数时遗漏await
调用无返回值的异步函数时,如果不小心遗漏了await,那么即使typescript环境处于严格模式,这种遗漏也不会报错,需要我们额外注意。
3 try-catch只能捕获同步代码块中的异常
1 |
|
try-catch语句只能捕获同步代码块中的异常,而requestIdleCallback
是一个异步函数。需要在回调函数内部使用try-catch语句来捕获异常。