Node.js异步执行流疑问:为何调用fileHandle.write会影响文件状态?
问题:为何
fileHandle.write('[1]')的存在会影响后续write操作的文件状态 我正在学习JavaScript和Node.js,仍对异步执行逻辑感到困惑。我知晓以下代码并非最佳实践,但想理解:为何是否调用fileHandle.write('[1]'),会影响fileHandle.write('[2]')执行时文件是否处于打开状态。我在Node.js文档中看到相关说明:
在未等待Promise决议(或拒绝)的情况下,多次对同一文件使用filehandle.write()是不安全的。这种场景应使用filehandle.createWriteStream()。
无错误代码
import {createServer} from 'node:http' import {open} from 'node:fs/promises' import {write} from 'node:fs' import {json} from 'node:stream/consumers' const serveur = createServer(async (request,response) => { const path = './tp_CRUD/storage/my_file.json' const fileHandle = await open(path, 'w+') try { await fileHandle.read() .then(() => { fileHandle.write('[1]') // <= 无错误,此时文件仍处于打开状态 json(request) // 将请求内容解析为JSON的Promise .then(() => { fileHandle.write('[2]') // <= 无错误,因[1]行的存在,文件仍保持打开 }) }) } catch { console.log('error with POST request') } finally { fileHandle.close() } response.end() }) serveur.listen('3000')
报错代码
import {createServer} from 'node:http' import {open} from 'node:fs/promises' import {write} from 'node:fs' import {json} from 'node:stream/consumers' const serveur = createServer(async (request,response) => { const path = './tp_CRUD/storage/my_file.json' const fileHandle = await open(path, 'w+') try { await fileHandle.read() .then(() => { json(request) // 将请求内容解析为JSON的Promise .then(() => { fileHandle.write('[2]') // <= 报错,文件已关闭,尽管处于try{}块中? }) }) } catch { console.log('error with POST request') } finally { fileHandle.close() } response.end() }) serveur.listen('3000')
报错栈信息
node:internal/fs/promises:436 const err = new Error('file closed'); ^ Error: file closed at fsCall (node:internal/fs/promises:436:17) at FileHandle.write (node:internal/fs/promises:207:12) at process.processTicksAndRejections (node:internal/process/task_queues:95:5) { code: 'EBADF', syscall: 'write' } Node.js v21.1.0
原因分析
核心问题在于异步操作的执行顺序和Node.js内部对文件句柄的引用管理:
1. 两段代码的共同执行流程
- 通过
await open获取到文件句柄fileHandle,进入try块 await fileHandle.read()会等待读取操作完成,随后执行.then()回调函数.then()内部的同步代码执行完毕后,json(request)是异步Promise,会被加入微任务队列,不会立即执行
2. 关键差异点
无错误代码场景
fileHandle.write('[1]')是异步操作(返回Promise),未使用await等待其完成。这个操作会触发Node.js内部对文件句柄的引用计数增加,同时该Promise的决议任务会被加入微任务队列,且排在json(request).then()的前面。- 当
.then()回调执行完毕后,代码回到await fileHandle.read()之后,继续执行到finally块的fileHandle.close()。但此时fileHandle.write('[1]')的微任务尚未执行,Node.js内部会保证:只要文件句柄还有未完成的异步操作,close()不会立即关闭文件,而是等待所有操作完成后再真正释放句柄。 - 等
json(request)的Promise决议完成、执行.then()里的write('[2]')时,文件句柄仍处于未关闭状态,因此不会报错。
报错代码场景
- 没有
fileHandle.write('[1]'),.then()内部仅触发了json(request)这个异步Promise,它被加入微任务队列。 .then()回调执行完毕后,代码直接走到finally块的fileHandle.close(),此时没有其他未完成的文件操作,close()会立即关闭文件句柄。- 后续
json(request)的Promise决议完成,执行write('[2]')时,文件已经被关闭,因此抛出Error: file closed。
3. 补充说明
fileHandle.close()本身也是异步操作(返回Promise),代码中未使用await等待其完成,这是问题的根源之一。无错误代码能运行只是巧合,并非正确写法。- Node.js文档的提示是合理的:未等待Promise决议就多次调用
write会导致异步操作顺序不可控,极易出现文件状态不一致的问题,正确做法是使用fileHandle.createWriteStream(),或者严格await每一个文件操作。
内容的提问来源于stack exchange,提问作者coding_chicken
相关产品推荐
相关产品推荐

