10+错误后如何推送到节点流?

How to push to Node stream after error in 10+?

我最近拿起了一些旧的流代码(在 8.x 是 LTS 时编写的)并尝试将其更新为 12.x。这导致我处理 ENOENT 文件错误的方式发生了有趣的变化。

这里有一个简化:

const { createServer } = require('http')
const { createReadStream } = require('fs')

const PORT = 3000

const server = createServer((req, res) => {
  res.writeHead(200, {
    'Content-Type': 'application/json'
  })

  const stream = createReadStream(`not-here.json`, {encoding: 'utf8'})

  stream.on('error', err => {
    stream.push(JSON.stringify({data: [1,2,3,4,5]}))
    stream.push(null)
  })

  stream.pipe(res)
})

server.listen(PORT)
server.on('listening', () => {
  console.log(`Server running at http://localhost:${PORT}/`)
})

在 Node 8 中,上面的代码工作正常。我能够拦截错误,向流中写入一些内容并让它正常关闭。

在 Node 10+(测试 10、12 和 13)中,调用我的错误回调时流已经被销毁。我无法在流中推送新内容并为客户端优雅地处理错误。

这是有意更改吗?我还能以一种对 clint 端好的方式处理这个错误吗?

一种可能。自己打开文件,只用已经成功打开的文件创建流。这将允许您在进入混乱的流错误处理机制之前处理 ENOENT(或打开文件时的任何其他错误)。流架构似乎最符合错误中止,而不是通过某些替代行为恢复。

const { createServer } = require('http');
const fs = require('fs');

const PORT = 3000;

const server = createServer((req, res) => {
    res.writeHead(200, {'Content-Type': 'application/json'});

    fs.open('not-here.json', {encoding: 'utf8'}, (err, fd) => {
        if (err) {
            // send alternative response here
            res.end(JSON.stringify({data: [1,2,3,4,5]}));
        } else {
            const stream = fs.createReadStream(null, {fd, encoding: 'utf8'});
            stream.pipe(res);
        }
    });
});

server.listen(PORT);
server.on('listening', () => {
    console.log(`Server running at http://localhost:${PORT}/`)
});

您也可以尝试在流中使用 autoDestroyautoClose 选项,看看是否有任何这些标志允许流仍然打开,以便您将数据推送到其中,即使文件创建了打开或读取错误。关于这些标志的文档不是很完整,因此需要将编程实验和研究代码结合起来,看看是否可以操纵它们,以便在您的流出现错误后仍然向流中添加数据。

jfriend00 的回答为我指明了正确的方向。 这是我解决这个问题的两种不同方法。我想要一个返回流而不是处理 req 处理函数中的错误的函数。这更像是我在实际代码中所做的。

处理来自流的错误:

就像上面一样,只是我小心地手动销毁了流。这是否正确处理了内部文件描述符?我觉得是的。

const server = createServer((req, res) => {
  res.writeHead(200, {
    'Content-Type': 'application/json'
  })

  getStream().pipe(res)
})


function getStream() {
  const stream = createReadStream(`not-here.json`, {
    autoClose: false,
    encoding: 'utf8'
  })

  stream.on('error', err => {
    // handling "no such file" errors
    if (err.code === 'ENOENT') {
      // push JSON data to stream
      stream.push(JSON.stringify({data: [1,2,3,4,5]}))

      // signal the end of stream
      stream.push(null)
    }

    // destory/close the stream regardless of error
    stream.destroy()

    console.error(err)
  })

  return stream
}

处理文件打开时的错误:

就像 jfriend00 建议的那样。

const { promisify } = require('util')
const { Readable } = require('stream')
const { open, createReadStream } = require('fs')

const openAsync = promisify(open)

const server = createServer(async (req, res) => {
  res.writeHead(200, {
    'Content-Type': 'application/json'
  })

  const stream = await getStream()
  stream.pipe(res)
})

async function getStream() {
  try {
    const fd = await openAsync(`not-here.json`)
    return createReadStream(null, {fd, encoding: 'utf8'})
  } catch (error) {
    console.log(error)

    // setup new stream
    const stream = new Readable()

    // push JSON data to stream
    stream.push(JSON.stringify({data: [1,2,3,4,5]}))

    // signal the end of stream
    stream.push(null)

    return stream
  }
}

我仍然更喜欢在流中进行更好的处理,但很想听听您为什么可以采用这种或另一种方式进行处理的原因。