善用 Promise.all
async_function 的出现,让异步代码写起来更方便的同时可能也会让人忽略一些性能问题,比如我今天优化的一个异步日志功能。
代码如下:
1 | async function doLog(work) { |
以上代码可以看出 work2
需要等待 work1
,且业务上确实需要保证 work1
的日志输出要先于 work2
,所以看起来这么写是没啥毛病的。
不过对于这个需求来说,仍然是可以优化的,通过重新设计 doLog
函数,让 work1
和 work2
既可以同时执行又可以保障日志的输出顺序。思路是:doLog
内部使用 Promise.all 同时执行多个异步任务,各自任务拥有各自的 log
队列,而这些队列又按照 works
顺序排列。
代码如下:
1 | async function doLog(...works) { |
这里假设 work1
的执行始终需要 2s,work2
的执行始终需要 1s。
那么理论上原版需要耗时 3s,优化版的耗时会始终等于耗时最长的任务也就是 2s,总共节约了 1s。显然 work 队列越大节约的时间还会越多。
Promise 缓存
请求数据是一个常见的需求,一般来说开发者都会设计一个支持数据缓存的请求层。然而更深一步想,除了数据缓存,请求本身是否也可以缓存复用呢?
场景如下:
A,B 两个组件都会请求一个设置了数据缓存的 **url: ‘/xxx’**。
假设 B
的请求刚好发生在 A
请求结束的时候,这时候数据缓存会命中,至始至终只发起了一次请求;那么如果 B
的请求刚好发生在 A
请求的过程中,数据缓存不会被命中,相同的请求会在间隔相当短的时间内发起两次。
所以,上述场景展示了数据缓存的不足。如果你的请求层刚好是一个基于 Promise 的设计,那么稍作改造就可以天然支持数据缓存和请求缓存。
代码如下:
1 | const cache = {} |
如此,上述情况 A 和 B 会复用同一个请求
善用 await 做归一化处理
好像很多人都不知道可以直接 await 一个非 Promise 函数,这在做兼容的时候很方便。
比如,写一个弹窗组件,onOk 支持传入一个普通函数或 Promise 函数。如果是 Promise 函数弹窗会在 resolve 后自动关闭。基于以上特性核心部分只需要:
1 | await this.onOk() |
而不用
1 | const ret = this.onOk() |