假若有两个promise: a, b,现在它们都Promise.race([a,b])
所包裹。
此时,如果a先完成了resolve,race也就有了返回值。
然而,b却迟迟没有被resolve或者reject……
结果会带来隐式的内存问题,就是a看上去被释放了,但其实没有。
v8论坛上有类似的bug提交:https://bugs.chromium.org/p/v8/issues/detail?id=9858
简单地模拟一下实现:
function race(...promises){
return new Promise((resolve, reject)=>{
for(const p of promises){
p.then(resolve, reject)
}
})
}
我实际测试了一下,一些不同版本的v8似乎会有不同的表现,但终归是内存泄漏了,只是好像v12的是直接泄漏了捕捉不到,v13是能在内存堆栈里头看到。
我在工作的时候发现这个问题也是靠async_hooks的异步资源监控下才看到这个问题的存在。
解决方案其实也不难,核心问题就是消除引用:
- 首先我们需要一个
Promise.prototype.safeThen
的实现,因为Promise.prototype.then
是会返回出一个新的promise的。做法其实就是只使用一次then来代理实现。其返回值就是thened: { resolves: Set<Function>, rejects: Set<Function>, isFinished: boolean }
- 接着我们基于
safeThen
来实现Promise.safePromiseRace
,重点在于收集thened对象,并主动进行释放:
function safePromiseRace(...promises){
return new Promise((resolve, reject)=>{
const thenedList = []
const finished = ()=>{
thenedList.forEach(thened=>{
thened.resolves.delete(safeResolve);
thened.rejects.delete(safeReject);
})
}
const safeResolve = (v)=>{resolve(v); finished();}
const safeReject = (v)=>{reject(v); finished();}
for(const p of promises){
thenedList.push(p.safeThen(safeResolve, safeReject))
}
})
}