2016-11-28 53 views
1

任何人都可以提出處理具有錯誤日誌記錄的管道的慣用功能方式。示例命令式(在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庫嗎?

感謝。

+1

如果你想在一個錯誤拋棄管道,使用'Either'單子,但如果你只是想記錄錯誤,但繼續流水線,使用'Writer'單子。快速谷歌搜索發現兩個JS庫。 – 4castle

+1

也許你不需要monad。看看[驗證](https://github.com/folktale/data.validation)應用程序。它的行爲類似於'Either',但沒有短路,並且有一個旨在處理錯誤的詞彙。 – ftor

回答

0

在處理的每一步中,都會產生「字母計數」或「控制檯消息」結果。一個最簡單最直接的方式來解決這個問題是讓你第一遍創建這些值的異構列表,然後做第二遍的那些分成兩個同質名單:

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編程時一直在做這類事情。使用副作用將「已記錄」的值附加到某個隊列中比將所有事情重構爲使用複雜的monads和/或monad轉換器要簡單得多。

0

//這將是我的實現使用RamdaJS與民間故事

const fs = require('fs'); 
const R = require('ramda'); 
const Result = require('folktale/result'); 

const filesToProcess = ['1.txt','2.txt','non_existent_file.txt']; 

const processFile = function(file){ 
    try { 
     const count = fs.readFileSync(file,'utf8').length; 
     return Result.Ok(count); 
    } catch (e) { 
     return Result.Error(`Error whilst processing ${file}. It's skipped.\n`); 
    } 
}; 

const splitEithers = R.groupBy(el => (
    Result.Error.hasInstance(el)) 
     ? 'errors' 
     : 'counts' 
); 

const doMath = (processedFiles) => { 
    const totalCount = 
     processedFiles 
      .counts 
      .reduce((n, okNumber) => R.add(n, okNumber.getOrElse(0)), 0); 

    const errors = 
     processedFiles 
      .errors 
      .reduce((msg, errorMessage) => 
       msg + errorMessage.matchWith({Error: n => n.value})); 

    return [errors, totalCount]; 
}; 

const FX = ([errors, count]) => { 
    console.log(`Total word count: ${count}`); 
    console.log(errors); 
}; 

const main = R.pipe(
    R.map(processFile), 
    splitEithers, 
    doMath, 
    FX 
); 

main(filesToProcess); 
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()來代替。

最後的變化,這是非常小的,只是到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)

相關問題