I'm learning javascript and nodejs and still strugling with the asynchronous execution
I know the following code is far from best practice, but I need to understand why, depending on whether or not fileHandle.write('[1]') is called, the file will still be opened or not when fileHandle.write('[2]') is called.
I found this in the nodejs documentation, I guess it's related, but I'd like to understand how things work: "It is unsafe to use filehandle.write() multiple times on the same file without waiting for the promise to be resolved (or rejected). For this scenario, use filehandle.createWriteStream()."
Code without error:
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]') // <= no error as the file is still opened at this point
json(request) // promise parsing request content to json
.then(() => {
fileHandle.write('[2]') // <= no error as file is still open somehow thanks to [1] line
})
})
} catch {
console.log('error with POST request')
} finally {
fileHandle.close()
}
response.end()
})
serveur.listen('3000')
Code with error:
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) // promise parsing request content to json
.then(() => {
fileHandle.write('[2]') // <= error as file is closed despite being in the try{} block ??
})
})
} catch {
console.log('error with POST request')
} finally {
fileHandle.close()
}
response.end()
})
serveur.listen('3000')
here is the callstack if it can help somehow
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
The way you have the calls laid starts two async chains that lead to a race condition as there's no guarantee the write call happens before the close call happens.
When you have
await fileHandle.read()
.then(() => {
// A
json(request)
.then(() => {
// B
fileHandle.write('[2]') // <= error as file is closed despite being in the try{} block ??
})
You are queueing a new async task B with no relation to A and execution may proceed before B runs. If you want to chain them together you have to have the then()
function return a Promise to append to the chain.
If you want to continue using the then()
notation you need to chain them together like so:
await fileHandle.read()
.then((fileData) => json(request) )
.then((jsonData) => fileHandle.write('[2]') )
Or like so
await filehandle.read()
.then((fileData) => {
// do some stuff
return json(request);
}).then( (jsonData) => fileHandle.write('foobar') )
This way the await
call will wait for the entire chain.
Alternatively, you could lose the then()
notations altogether and just use await
s like so:
let fileData = await fileHandle.read()
let jsonData = await json(request)
await fileHandle.write('[2]')