NodeJS:需要习惯用法:读取目录中的文件,连接,转换,写入

我正在尝试在Node中编写一个内容manglement程序。 我是一个老的Ruby / Perl / Shell多年的手,我似乎无法得到在这些语言中工作的简单代码在Node中看起来类似,简单。

任务 :find所有的*.md文件,读取它们(按ls顺序),转换它们,用括号注释和页脚注释括起来。 这些文件按顺序有一个Markdown,在汇编和转换时,是一个合理的HTML文档。 这是一个shell实现:

echo '<!-- Generate at:' $(date) ' -->' $(ls *.md |xargs cat|markdown)'<!-- Copyright Mumble-demo Inc. -->'

生成所需的HTML:

 <!-- Generate at: Tue Jun 6 08:25:59 EDT 2017 --> <h1>This is a Markdown File</h1> <h2>Heading 1</h2> <p>Inside of markdown we can create many interesting items</p> <ul> <li>such</li> <li>as</li> <li>lists</li> </ul><!-- Copyright Mumble-demo Inc. --> 

Ruby同样合理…

 #!/usr/bin/env ruby require 'kramdown' HEADER = "<!-- Generated at #{Time.now} -->\n" FOOTER = "\n<!-- Copyright Mumble-demo Inc. -->" OUTPUT = File.open("./output", "w") results = Dir.glob("*.md").map { |f| File.open(f).readlines.join() }.reduce(:+) OUTPUT.print(HEADER, Kramdown::Document.new(results).to_html, FOOTER) 

但我无法弄清楚如何以节点感觉正确(TM)

感觉错误(TM)的方式是与同步接口:

 const fs = require("fs") const marked = require("marked") const HEADER = `<!-- Generated at ${new Date()} -->\n` const FOOTER = `\n<!-- Copyright Mumble-demo Inc. -->` fs.readdir(".", (err, files) => { if (err) throw err; let markdownFiles = files.filter((f) => f.endsWith(".md")) let content = markdownFiles.reduce((memo, fileName) => { return memo + fs.readFileSync(fileName, 'utf8') }, "") let contentString = [HEADER, marked(content), FOOTER].reduce((m, i) => m + i, "") fs.writeFileSync("derp", contentString); console.log(contentString); }) 

感觉正确,但我无法工作(TM)的方法是:

  1. build立阅读stream
  2. 将它们pipe理为降价转换stream
  3. 打开一个输出stream并将转换后的数据redirect到它

好消息是 – 这种方法的工作,直到把头部注释放在顶部和底部。 他们生活在代码中,而不是在文件系统中,所以我不能将它们“添加”为另一个文件,将其转换为输出stream。 大多数方法都会产生:标题,页脚,stream式数据

很明显, pipe()工作是asynchronous工作的,在读取+变换工作完成之前,脚本打印会触发。 我已经尝试了可怕的(和破碎的) Promise链,最终没有奏效。

一种替代方法是将页眉和页脚转换为stream(看起来很奇怪…),并将其stream入输出stream(看起来很奇怪)。

我已经把这几个经验丰富的开发人员用这个…难道我们在这里错过了一些常见的习惯用法,或者实际上很难在Node中完成这个简单的任务吗?

思考:

  • 对于我的大部分shell脚本,我只是将文件内容同步读入string。 这种方法不能缩放,但通常不需要。 而且一切都比弦乐更容易。
  • 如果你做asynchronous操作:使用asynchronous函数和util.promisify()
  • 长期的asynchronous迭代和asynchronous生成器也将有助于这种情况。

您可以通过同步执行程序nsynjs运行它“感觉良好的一种方式”。 你的代码可能类似于这个工作例子:

MD-cat.js:

 var nsynjs = require('nsynjs'); var nsynFs = require('../wrappers/nodeFs'); // part of nsynjs package, needs to be added manually var synchronousCode = function(nsynFs) { var HEADER = "<!-- Generated at "+new Date()+" -->\n"; var FOOTER = "\n<!-- Copyright Mumble-demo Inc. -->"; var files = nsynFs.readdir(nsynjsCtx, ".").data; var content=""; for(var i=0; i<files.length; i++) { var file = files[i]; if(file.endsWith('.md')) content+=nsynFs.readFile(nsynjsCtx,file,"utf8").data; } nsynFs.writeFile(nsynjsCtx,"derp",HEADER+content+FOOTER); }; nsynjs.run(synchronousCode, {},nsynFs, function () { console.log('synchronousCode done') }); 

即使它看起来是同步的,它并不使用任何同步函数,因此它不会阻塞节点的事件循环。

试试Gulp ,这是当今最习惯的方式。

如果不能或不想要,使用Promise链,他们感觉像壳pipe。

 #!/usr/bin/env node 'use strict'; const Promise = require('bluebird'); const fs = Promise.promisifyAll(require('fs')); const path = require('path'); const marked = require('marked'); const HEADER = `<!-- Generated at ${(new Date()).toISOString()} -->`; const FOOTER = '<!-- Copyright Mumble-demo Inc. -->'; fs.readdirAsync(process.cwd()) .map((fileName) => Promise.all([fileName, fs.statAsync(fileName)])) .filter(([fileName, stat]) => stat.isFile() && path.extname(fileName) === '.md') .call('sort', ([a], [b]) => a.localeCompare(b, 'en-US')) .map(([mdFileName]) => fs.readFileAsync(mdFileName, 'utf8')) .then((mdFiles) => { let out = [HEADER, marked(mdFiles.join('\n')), FOOTER].join('').replace(/\n/g, ''); console.log(out); return fs.writeFileAsync('out.html', out); }) .catch((err) => { console.error(err); process.exit(1); }); 

思考:

  • 切勿在Node中编写同步代码,您将永远后悔。
  • 承诺链最适合这样的任务。
  • stat.isFile()和sort()只是安全function,在bash和Ruby示例中缺less。 删除它们可以保存两行代码。
  • 在大多数情况下,Date.prototype.toString()的用法应该被认为是一个错误,因为输出是不可预测的,它是平台和特定于语言环境的。
  • 节点stream是矫枉过正,直到你处理大文件,这通常不是降价任务的情况下。
  • Shellpipe道也不使用文件系统stream,并将所有内容加载到内存中。 效率大致相同。