calc
calc是一個我們想要做剖析(性能分析)的異步函數。按照慣例,它的最後一個參數是一個callback 。我們像這樣使用calc :
calc(arg, (err, res) => console.log(err || res))
或許,最簡單的對calc這樣的函數來剖析性能的方法是,增加一個計時邏輯到我們需要分析的地方:
const t0 = Date.now()calc(arg, (err, res) => { const t1 = Date.now() console.log(`Log: time: ${t1 = t0}`) console.log(err || res)})但是,這不是一個可複用的解決方案。每一次我們想要對一個函數計時,我們得引入一個t0 在外層作用域並且改變callback來測量和記錄時間。
對我來說理想的方式是能夠僅僅通過包裝一個異步函數就能夠對它進行計時:
timeIt(calc)(arg, (err, res) => console.log(err || res))
timeIt需要能夠很好地對每一個異步函數完成剖析和記錄執行時間。
注意到timeIt(calc)有與原始的calc 函數同樣的函數簽名,即它們接受同樣的參數和返回同樣的值,它只是增加了一個特性到cale 上(能夠被記錄時間的特性)。
calc 和timeIt(calc) 在任意時刻可以相互替代。
timeIt本身是一個高階函數,因為它接受一個函數並返回一個函數。在我們的例子裡,它接受calc 異步函數,並返回一個函數與calc 有同樣的參數和返回值。
下面演示我們如何實現timeIt 函數:
const timeIt = R.curry((report, f) => (...args) => { const t0 = Date.now() const nArgs = R.init(args) const callback = R.last(args) nArgs.push((...args) => { const t1 = Date.now() callback(...args) report(t1 - t0, ...args) }) f(...nArgs)})const timeIt1 = timeIt( (t, err, res) => console.log(`Log: ${err || res} produced after: ${t}`))const calc = (x, y, z, callback) => setTimeout(() => callback(null, x * y / z), 1000)calc(18, 7, 3, (err, res) => console.log(err || res))timeIt1(calc)(18, 7, 3, (err, res) => console.log(err || res))這個timeIt實現接受兩個參數:
report: 一個函數用來生成剖析結果
f: 我們想要做剖析的異步函數
timeIt1是一個方便實用的功能函數,它只是用console.log記錄時間測量結果。我們通過給更通用的timeIt函數傳入report參數來定義它。
我們實現了目標,現在我們可以僅僅將異步函數包裝在timeIt1中就可以對它計時了:
timeIt1(calc)(18, 7, 3, (err, res) => console.log(err || res))
通用的timeIt函數接收一個report回調函數和一個異步函數並返回一個新的異步函數,這個異步函數與原函數有同樣的參數和返回值。我們可以這麼使用:
timeIt( (time, ...result) => // report callback: log the time , asyncFunc)( parameters…, (...result) => // result of the async function)
現在讓我們深入timeIt的實現。我們可以簡單地生成一個通用函數類似timeIt1 ,因為timeIt使用R.curry科里化了。
我不打算在這篇文章裡討論科里化,但是下面這段代碼演示了科里化的主要用法:
const f = R.curry((x, y) => x + y)f(1, 10) // == 11f(1)(10) // == 11const plus1 = f(1)plus1(10) // == 11
另一方面,這種方式實現的timeIt 有幾個問題:
(...args) => { const t1 = Date.now() callback(...args) report(t1 ― t0, ...args)}這是一個匿名函數(又名lambda,callback),它在原函數異步執行之後被調用。主要的問題是這個函數沒有處理異常的機制。如果callback拋出異常, report就永遠不會被調用。
我們可以添加一個try / catch到這個lambda函數里,然而問題的根源是callback和report是兩個void函數,它們沒有關聯在一起。 timeIt包含兩個延續(continuations)( report和callback )。如果我們只是在console下記錄執行時間或者如果我們確定不論report還是callback都不會拋出異常,那麼一切正常。但是如果我們想要根據剖析結果來執行一些行為(所謂的自動擴容)那麼我們需要強化和釐清我們的程序中的延續序列。
好了,以上這篇文章的全部內容,希望對大家的學習和工作有所幫助,如果有疑問可以留言交流。