是否有更优雅的方式来读取然后用节点js流写相同的文件*

时间:2017-09-29 06:54:06

标签: node.js

我想读取文件然后用through2更改它然后写入相同的文件,代码如下:

const rm = require('rimraf')
const through2 = require('through2')
const fs = require('graceful-fs')
// source file path
const replacementPath = `./static/projects/${destPath}/index.html`
// temp file path
const tempfilePath = `./static/projects/${destPath}/tempfile.html`
// read source file then write into temp file
await promiseReplace(replacementPath, tempfilePath)    
// del the source file
rm.sync(replacementPath)
// rename the temp file name to source file name
fs.renameSync(tempfilePath, replacementPath)
// del the temp file
rm.sync(tempfilePath)

// promiseify readStream and writeStream
function promiseReplace (readfile, writefile) {
  return new Promise((res, rej) => {
    fs.createReadStream(readfile)
      .pipe(through2.obj(function (chunk, encoding, done) {
        const replaced = chunk.toString().replace(/id="wrap"/g, 'dududud')
        done(null, replaced)
      }))
      .pipe(fs.createWriteStream(writefile))
      .on('finish', () => {
        console.log('replace done')
        res()
      })
      .on('error', (err) => {
        console.log(err)
        rej(err)
      })
  })
}

以上代码有效,但我想知道我可以让它更优雅吗?

我还尝试了一些像node-temp

这样的临时库

不幸的是,它也无法将readStream和writeStream读入同一个文件,我打开a issues来解决这个问题。

所以任何人都知道更好的方法告诉我,非常感谢你。

2 个答案:

答案 0 :(得分:9)

您可以通过消除不必要的依赖关系并使用更新的简化构造函数来使代码更加优雅。

const fs = require('fs');
const util = require('util');
const stream = require('stream');
const tempWrite = require('temp-write');

const rename = util.promisify(fs.rename);

const goat2llama = async (filePath) => {
    const str = fs.createReadStream(filePath, 'utf8')
        .pipe(new stream.Transform({
            decodeStrings : false,
            transform(chunk, encoding, done) {
                done(null, chunk.replace(/goat/g, 'llama'));
            }
        }));
    const tempPath = await tempWrite(str);
    await rename(tempPath, filePath);
};

测试

AVA测试证明它有效:

import fs from 'fs';
import path from 'path';
import util from 'util';
import test from 'ava';
import mkdirtemp from 'mkdirtemp';
import goat2llama from '.';

const writeFile = util.promisify(fs.writeFile);
const readFile = util.promisify(fs.readFile);

const fixture = async (content) => {
    const dir = await mkdirtemp();
    const fixturePath = path.join(dir, 'fixture.txt');
    await writeFile(fixturePath, content);
    return fixturePath;
};

test('goat2llama()', async (t) => {
    const filePath = await fixture('I like goats and frogs, but goats the best');
    await goat2llama(filePath);
    t.is(await readFile(filePath, 'utf8'), 'I like llamas and frogs, but llamas the best');
});

有关变化的一些事项:

  • 不再需要通过2。过去常常设置passthrough或转换流是一种痛苦,但由于simplified construction API,情况已经不是这样了。
  • 你可能也不需要优雅的fs。除非您正在执行大量并发磁盘I / O,EMFILE通常不是问题,尤其是现在Node已经变得更加智能化了文件描述符。但该库确实有助于解决Windows上的防病毒软件导致的临时错误,如果这对您来说是个问题。
  • 你绝对不需要rimraf。您只需要fs.rename()。它类似于命令行上的mv,有一些细微差别使它与众不同,但这里的差异并不是非常重要。重点是在重命名那里的文件后,临时路径上没有任何内容。
  • 我使用了temp-write,因为它会为您生成一个安全的随机文件路径并将其放入OS临时目录(它会立即自动清理),此外它还会处理将流转换为{{1}为你而且处理一些有关错误的边缘情况。 披露:我在Promise中编写了流实现。 :)

总的来说,这是一个不错的改进。但是,评论中仍然讨论了boundary problem。幸运的是,你不是第一个遇到这个问题的人!我不会称actual solution特别优雅,当然不是你自己实施的。但是replacestream可以帮到你。

temp-write

也...

  

我不喜欢临时文件

确实,临时文件通常很糟糕。但是,在这种情况下,临时文件由设计良好的库管理,并存储在安全的偏远位置。几乎没有机会与其他流程发生冲突。即使const fs = require('fs'); const util = require('util'); const tempWrite = require('temp-write'); const replaceStream = require('replacestream'); const rename = util.promisify(fs.rename); const goat2llama = async (filePath) => { const str = fs.createReadStream(filePath, 'utf8') .pipe(replaceStream('goat', 'llama')); const tempPath = await tempWrite(str); await rename(tempPath, filePath); }; 以某种方式失败,操作系统也会清理该文件。

也就是说,您可以使用fs.readFile()fs.writeFile()而不是流媒体来完全避免使用临时文件。前者也使文本替换更容易,因为您不必担心块边界。您必须选择一种方法或另一种方法,但对于非常大的文件,除了manually chunking the file之外,流式传输可能是唯一的选择。

答案 1 :(得分:1)

Streams在这种情况下是无用的,因为它们会返回你可以破坏你正在搜索的字符串的文件块。您可以使用流,然后合并所有这些块以获取内容,然后替换您需要的字符串,但这将是更长的代码,只会引发一个问题:why do you read file by chunks if you don't use them ?

实现目标的最短途径是:

let fileContent = fs.readFileSync('file_name.html', 'utf8')
let replaced = fileContent.replace(/id="wrap"/g, 'dududud')
fs.writeFileSync('file_name.html', replaced)

所有这些功能都是synchronous,因此您无需宣传它们