前端杂记
前端杂记
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
encodingis'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语句来捕获异常。









