起因

最近想要将云存储中的文件去重。因为有现成的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

posted on 2019-07-25 15:08  步孤天  阅读(329)  评论(0编辑  收藏  举报