读写文件前检查文件/文件夹存在性的方案是否存在竞态条件?
你的代码仍存在竞态条件
是的,你当前的代码依然有竞态条件风险,问题出在检查操作(fs.access)和后续的创建/写入操作之间存在时间窗口:
举两个典型场景:
- 当
checkIfFolderOrFileExist返回文件/文件夹不存在后,在你调用createFolder之前,另一个进程或线程创建了目标文件夹,此时你的createFolder会因为文件夹已存在而抛出错误。 - 当
checkIfFolderOrFileExist返回文件存在后,在你调用writeToTheFile之前,另一个进程或线程删除了该文件,若文件所在文件夹也被删除,writeFile会直接报错;即使文件夹还在,若你的业务逻辑依赖“覆盖原有文件”的预期,也可能出现不符合预期的行为。
如何彻底规避竞态条件
正确的思路是直接执行目标操作,然后捕获并处理操作过程中出现的错误,因为提前检查和执行操作不是原子性的,中间的时间差总会有被外部干扰的可能。
针对你的场景,可以调整代码如下:
方案1:利用fs.mkdir的recursive参数(推荐)
将createFolder的recursive设为true,这样即使文件夹已存在也不会报错,跳过提前检查的步骤:
const createFolder = () => { return new Promise((resolve, reject) => { fs.mkdir(folderPath, { recursive: true }, (err) => { if (err) { reject(err); } else { resolve("done"); } }); }); }; const callBothPromiseFunction = async () => { try { // 先确保文件夹存在,不管之前有没有 await createFolder(); // 直接写入文件,不存在则创建,存在则覆盖 await writeToTheFile(someRandomData); // 其他逻辑 } catch (err) { // 统一处理错误 console.error(err); } };
方案2:直接写入,捕获路径不存在的错误后再创建文件夹重试
如果不需要提前确保文件夹存在,也可以直接尝试写入,捕获ENOENT(路径不存在)错误后再创建文件夹并重试:
const callBothPromiseFunction = async () => { try { await writeToTheFile(someRandomData); } catch (err) { if (err.code === 'ENOENT') { // 路径不存在,先创建文件夹 await createFolder(); // 重试写入 await writeToTheFile(someRandomData); } else { // 处理其他错误 throw err; } } // 其他逻辑 };
总结
所有“先检查后操作”的文件系统操作模式都存在竞态条件风险,因为检查和操作不是原子操作。正确的做法是直接执行操作,通过错误处理来应对不同的前置状态,这也是Node.js官方文档推荐的方式。
内容的提问来源于stack exchange,提问作者JustANewCoder
相关产品推荐
相关产品推荐

