Calc
calc is an asynchronous function that we want to do analysis (performance analysis). By convention, its last parameter is a callback . We use calc like this:
calc(arg, (err, res) => console.log(err || res))
Perhaps the easiest way to analyze performance on a function like calc is to add a timing logic to where we need to analyze:
const t0 = Date.now()calc(arg, (err, res) => { const t1 = Date.now() console.log(`Log: time: ${t1 = t0}`) console.log(err || res)}) However, this is not a reusable solution. Every time we want to time a function, we have to introduce a t0 in the outer scope and change callback to measure and record the time.
The ideal way for me is to be able to time it just by wrapping an asynchronous function:
timeIt(calc)(arg, (err, res) => console.log(err || res))
timeIt needs to be able to perform analysis and record execution time for each asynchronous function well.
Note that timeIt(calc) has the same function signature as the original calc function, that is, they accept the same parameters and return the same value, it just adds a feature to the cale (a feature that can be recorded time).
calc and timeIt(calc) can be replaced by each other at any time.
timeIt itself is a higher-order function because it accepts a function and returns a function. In our example, it accepts a calc asynchronous function and returns a function with the same parameters and return value as calc.
The following shows how we implement the timeIt function:
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)) This timeIt implementation accepts two parameters:
report: A function is used to generate analysis results
f: Asynchronous function we want to do analysis
timeIt1 is a convenient and practical function, it just uses console.log to record time measurement results. We define it by passing in report parameter to the more general timeIt function.
We achieved the goal, and now we can just wrap the asynchronous function in timeIt1 and time it is timed:
timeIt1(calc)(18, 7, 3, (err, res) => console.log(err || res))
The general timeIt function receives a report callback function and an asynchronous function and returns a new asynchronous function. This asynchronous function has the same parameters and return value as the original function. We can use this:
timeIt( (time, ...result) => // report callback: log the time , asyncFunc)( parameters…, (...result) => // result of the async function)
Now let's dive into the implementation of timeIt . We can simply generate a general function like timeIt1 , because timeIt is corylated using R.curry .
I don't plan to discuss coriculization in this post, but the following code demonstrates the main usage of coriculization:
const f = R.curry((x, y) => x + y)f(1, 10) // == 11f(1)(10) // == 11const plus1 = f(1)plus1(10) // == 11
On the other hand, there are several problems with timeIt implemented in this way:
(...args) => { const t1 = Date.now() callback(...args) report(t1 ― t0, ...args)} This is an anonymous function (also known as lambda, callback) which is called after the original function is executed asynchronously. The main problem is that this function has no mechanism to handle exceptions. If callback throws an exception, report will never be called.
We can add a try / catch to this lambda function, but the root of the problem is that callback and report are two void functions, and they are not associated. timeIt contains two continuations ( report and callback ). If we just record the execution time under console or if we are sure that neither report nor callback will throw exceptions, then everything is OK. But if we want to perform some behavior based on the analysis results (so-called automatic scaling) then we need to strengthen and clarify the continuation sequence in our program.
Okay, I hope the full content of this article will be helpful to everyone's study and work. If you have any questions, you can leave a message to communicate.