【问题标题】:What's the idiomatic functional way to handle processing pipeline with error logging?使用错误日志记录处理处理管道的惯用功能方式是什么?
【发布时间】:2017-04-11 19:17:29
【问题描述】:

任何人都可以提出惯用的函数式方法来处理带有错误日志记录的管道。示例命令式样式(在 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 库吗?

谢谢。

【问题讨论】:

  • 如果您想在出错时放弃管道,请使用 Either monad,但如果您只想记录错误但继续管道,请使用 Writer monad。快速谷歌搜索找到了两者的 JS 库。
  • 也许你不需要一个 monad。查看validation 应用程序。它的行为类似于Either,但没有短路,并且它有一个旨在处理错误的词汇表。

标签: javascript functional-programming idioms


【解决方案1】:

这是一个惯用的 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()

最后的改动非常小,只是将0 的可选参数提供给Array#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)

【讨论】:

    【解决方案2】:

    在处理的每个步骤中,您都会产生“信件计数”或“控制台消息”结果。解决此问题的最简单最直接的方法之一是让您的第一遍创建这些值的异构列表,然后再执行第二遍将它们分成两个同类列表:

    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,就像您已经在命令式 sn-p 中所做的那样。这样一来,您就不需要将汇总事物和记录错误的交叉关注点混合在一起,并且您最终会得到与坚持“纯功能”解决方案所获得的非常相似的结果。

    当我在 Ocaml 中编程时,我一直在做这种事情。使用副作用将“记录”值附加到某个队列中比重构所有内容以使用复杂的 monad 和/或 monad 转换器要简单得多。

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 2013-02-13
      • 2020-12-04
      • 1970-01-01
      • 2015-03-03
      • 2011-03-05
      • 2011-06-03
      • 2014-03-05
      相关资源
      最近更新 更多