无法理解此异步示例中的执行流

Can't understand the execution flow in this asynchronous sample

提问人:coding_chicken 提问时间:10/31/2023 更新时间:10/31/2023 访问量:55

问:

我正在学习 javascript 和 nodejs,并且仍在为异步执行而苦苦挣扎

我知道以下代码远非最佳实践,但我需要了解为什么,根据是否调用 fileHandle.write('[1]'),在调用 fileHandle.write('[2]') 时仍会打开文件。

我在nodejs文档中找到了这一点,我想它是相关的,但我想了解事情是如何工作的: “在同一个文件上多次使用 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]') // <= 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')

有错误的代码:

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')

这是调用堆栈,如果它能以某种方式提供帮助

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
JavaScript 节点.js 异步 async-await http-post

评论


答:

0赞 leitning 10/31/2023 #1

发出调用的方式会启动两个异步链,从而导致争用条件,因为无法保证写入调用在关闭调用发生之前发生。

当您有

await fileHandle.read()
        .then(() => {
            // A
            json(request)
            .then(() => {
                // B
                fileHandle.write('[2]') // <= error as file is closed despite being in the try{} block ??
            })

您正在对与 A 无关的新异步任务 B 进行排队,并且执行可能会在 B 运行之前继续。如果要将它们链接在一起,则必须让函数返回一个 Promise 以附加到链中。then()

如果你想继续使用符号,你需要把它们链接在一起,如下所示:then()

await fileHandle.read()
        .then((fileData) => json(request) )
        .then((jsonData) => fileHandle.write('[2]') )

或者像这样

await filehandle.read()
  .then((fileData) => {
    // do some stuff
    return json(request);
  }).then( (jsonData) => fileHandle.write('foobar') )

这样,调用将等待整个链。await

或者,您可以完全丢失符号,而像这样使用 s:then()await

let fileData = await fileHandle.read()
let jsonData = await json(request) 
await fileHandle.write('[2]')

评论

0赞 coding_chicken 10/31/2023
谢谢它有帮助!如果我需要同时处理来自同一链(在其末尾)中 read()、json()、write() (和其他异步函数)的数据,我是否应该将它们的数据压缩到一个对象中(在链之前声明),或者是否有合适的方法来做到这一点?
0赞 leitning 10/31/2023
在这种情况下,你会做.developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/......await Promise.all([ fileHandle.read(), json(request), fileHandle.write('foo') ])