使用错误记录处理处理管道的惯用功能方法是什么?

时间:2016-11-28 06:01:41

标签: javascript functional-programming idioms

任何人都可以建议使用惯用的功能方式来处理带有错误记录的管道。示例命令式样式(在JavaScript中):

const filesToProcess = ['file1.txt','file2.txt','non_existent_file.txt'];

var totalLetterCountImperative = 0;
for (var i = 0; i < filesToProcess.length; i++){
    try {
        totalLetterCountImperative += fs.readFileSync(filesToProcess[i],'utf8').length;
    } catch (e) {
        console.log("There is an error whilst processing file: " + filesToProcess[i] + ". Hence, it's skipped. Error: " + e.message);
    }
}
console.log("Total Letter Count: " + totalLetterCountImperative);

以下尝试有效,但看起来很笨拙和笨拙。而且,它并非通用于所有错误:

const filesToProcess = ['file1.txt','file2.txt','non_existent_file.txt'];

const totalLetterCount = filesToProcess
                          .filter(f => fs.existsSync(f))
                          .map(f => fs.readFileSync(f,'utf8').length)
                          .reduce((a,b) => a+b);

filesToProcess
  .filter(f => !fs.existsSync(f))
  .map(f => console.error("There is an error whilst processing file: " + f +". Hence it's skipped. Error: File doesn't exist"));

console.log("Total Letter Count: " + totalLetterCount);

我读到了Either的用法。如果它确实是惯用的方式,有人可以给出一个例子并建议一个好的JavaScript库使用吗?

感谢。

2 个答案:

答案 0 :(得分:0)

在处理的每个步骤中,您都会产生一个&#34;字母数&#34;或者一个&#34;控制台消息&#34;结果。解决此问题的最简单最直接的方法之一是让您的第一个传递创建这些值的异构列表,然后再进行第二次传递以将它们分成两个同类列表:

const filesToProcess = ['file1.txt','file2.txt','non_existent_file.txt'];

var results = filesToProcess.map(function(f){
    try {
        var n = readFileSync(filesToProcess[i],'utf8').length;
        return {tag:"count", value: n};
    } catch (e) {
        var msg = "There is an error whilst processing file: " + filesToProcess[i] + ". Hence, it's skipped. Error: " + e.message);
        return {tag:"err", value:msg};
    }
})

var totalLetterCount =
    results
      .filter(r => r.tag === "count")
      .map(r => r.value)
      .reduce((a,b)=> a + b);

results
  .filter(r => t.tag == "error")
  .map(r => r.value)
  .map(msg => console.error(msg));

但是,在我看来,更好的解决方案可能是利用副作用并使用console.error,就像你已经在命令性片段中做的那样。通过这种方式,您不需要将总结事物和记录错误的交叉问题混合在一起,如果您坚持使用纯粹功能性的产品,那么最终会得到与您所获得的相似的东西。溶液

当我在Ocaml中编程时,我总是这样做。使用副作用附加&#34;记录&#34;更简单值到队列的地方比重构所有东西以使用复杂的monad和/或monad变换器。

答案 1 :(得分:0)

这是一种仅使用原生模块的惯用ES6方法:

const fs = require('fs')
const { promisify } = require('util')
const access = promisify(fs.access)
const readFile = promisify(fs.readFile)

const filesToProcess = ['file1.txt', 'file2.txt', 'non_existent_file.txt']

Promise.all(filesToProcess.map(file => access(file, fs.constants.R_OK)
  .then(() => readFile(file, 'utf8'))
  .catch(error => {
    console.log(`There is an error whilst processing file: ${file}. Hence, it's skipped. Error: ${error.message}`)

    return { length: 0 }
  })
)).then(files => {
  const totalLetterCount = files.reduce((a, b) => a.length + b.length, 0)

  console.log(`Total Letter Count: ${totalLetterCount}`)
})

util.promisify()允许您将回调式函数转换为异步函数(换句话说,返回Promise的函数)。这允许我们避免使用同步函数,并在等待后台进程时释放对线程的控制,以便线程可以同时执行其他任务。

此外,由于fs.exists()目前已被弃用,我选择使用fs.access()

最后一个非常小的变化只是提供0Array#reduce()的可选参数,因此length 1的数组仍会按预期正确缩小,而不是像这样返回第一个元素:

const totalLength = [{ length: 5 }].reduce((a, b) => a.length + b.length)
// since we didn't supply the optional argument, we didn't get `5` like we expected
console.log(totalLength)