起因
最近想要将云存储中的文件去重。因为有现成的Nodejs的API,所以打算用Nodejs实现此功能。
伪代码如下:
scanDir = function(uri){
return new Promise(function(resove, reject) {})
}
getFileInfo = function(uri){
return new Promise(function(resove, reject) {})
}
dealDir = aysnc function(uri) {
await scanDir(uri).then(function(res){
for (v of res) {
if (res.type === "Folder") {
dealDir(uri + '/' + v);
} else {
getFileInfo(uri + '/' + v).then(function(res){
//将文件信息存入数据库
})
}
}
}).catch(function(){})
}
递归什么的,用起来得心应手,在加上Promise这种大杀器,配合await用起来更是无人能挡。几百个文件的测试没问题,但真正运行起来之后,爆栈了。
分析
按道理讲,我只有3层目录,就算递归也不会有多少函数入栈。那么到底是什么原因呢?
因为Promise的递归容易出问题,比如上面的例子,虽然dealDir里面的scanDir函数被await了,但是dealDir函数本身还是压在栈里,并没有阻塞运行。
这样一层层地dealDir压入栈,迟迟等不到scanDir函数回调的响应导致了最终的爆栈。
如图:
解决方法
最后我选择了一种相对安全的方式:避免递归,用队列处理。
伪代码如下:
scanDir = function(uri){
return new Promise(function(resove, reject) {})
}
getFileInfo = function(uri){
return new Promise(function(resove, reject) {})
}
dealDir = aysnc function(uri) {
let folders = []
folders.push(uri)
while (folders.lenth > 0) {
let tmpfolder = folders.shift();
await scanDir(tmpfolder).then(function(res){
for (v of res) {
if (res.type === "Folder") {
folders.push(tmpfolder + '/' + v);
} else {
getFileInfo(tmpfolder + '/' + v).then(function(res){
//将文件信息存入数据库
})
}
}
}).catch(function(){})
}
}
参考资料
了解JavaScript的工作原理可以参考:
美团面试题:https://segmentfault.com/a/1190000015057278
JavaScript是如何工作的:
https://github.com/xitu/gold-miner/blob/master/TODO/how-javascript-works-event-loop-and-the-rise-of-async-programming-5-ways-to-better-coding-with.md