Explain Codes LogoExplain Codes Logo

How do I measure the execution time of JavaScript code with callbacks?

javascript
callbacks
performance
async-await
Alex KataevbyAlex Kataev·Aug 8, 2024
TLDR

Accurately timing JavaScript operations with callbacks involves using the performance.now() method. Capture timestamps at the moment your operation starts and when it ends (i.e., within the callback). The difference gives the true execution time.

const start = performance.now(); asyncFunction(() => { const duration = performance.now() - start; console.log(`Time taken: ${duration} ms`); });

Ensure the second timestamp is set within the callback to measure correctly, considering potential asynchronous operations.

High Precision with hrtime

In Node.js, you can use process.hrtime() for high-resolution timing, capable of measuring time down to the nanosecond level. Here's an example of converting it to milliseconds:

const start = process.hrtime(); asyncOperation(() => { const diff = process.hrtime(start); const durationInMilliseconds = (diff[0] * 1e9 + diff[1]) / 1e6; // Nano to milli conversion magic console.log(`Execution duration: ${durationInMilliseconds} ms`); // Still surprised it's not ∞ });

Don't forget to reset hrtime between measurements if you plan on timing multiple operations sequentially! Be sure to have sound error handling and use the Performance API for reliable asynchronous operations tracking.

Timing Iterative Async Operations

Timing individual operations within asynchronous loops, such as database insertions, can be handled like this:

let completedOps = 0; const LIMIT = 10; // It's over 9000!!!...oh wait, it's just 10. console.time('dbInsertTimer'); // Start the clock! for (let i = 0; i < LIMIT; i++) { insertIntoDatabaseAsync(data, (err) => { if (err) { console.error(`Insert error at ${i}:`, err); // Always handle possible errors. } else { console.log(`Saved record ${i}`); // Ding ding ding! One more inserted. if (++completedOps === LIMIT) { console.timeEnd('dbInsertTimer'); // Time's up! } } }); }

The LIMIT variable and incrementing completedOps ensure that the timer doesn't stop until every operation has completed its run.

Handling Timing Issues in Complex Scenarios

Certain challenges, such as inconsistent callback behavior or drastic operation times variability, may plague your timing measurements. For example, by using debug variables, you can fine-tune how much timing information to log. You might also measure timing around file operations with callbacks, implementing a function like send_html.

Precision Timing with Performance APIs

For extreme precision tracking, use Performance APIs. The performance.mark() and performance.measure() functions provide excellent control of timing in complex scenarios:

// Node.js 12.x and above const { performance, PerformanceObserver } = require('perf_hooks'); performance.mark('A'); // Let's call this "before" asyncOperation(() => { performance.mark('B'); // And this... "after" performance.measure('A to B', 'A', 'B'); // Now, tell us the "difference" // Retrieve the timing with `performance.getEntriesByName` const [measure] = performance.getEntriesByName('A to B'); console.log(`Execution time: ${measure.duration} ms`); // Cleanup performance.clearMarks(); // Say no to littering! performance.clearMeasures(); // Especially not in memory... });

The PerformanceObserver class is your friend when you need real-time performance metrics during your application's lifecycle.

Converting hrtime into Seconds for Readability

Consider implementing a helper function to convert hrtime into something more readable, like seconds:

function hrtimeToSeconds(hrtime) { return (hrtime[0] + (hrtime[1] / 1e9)).toFixed(3); } const start = process.hrtime(); // Start the timer. asyncOperation(() => { console.log(`Execution time: ${hrtimeToSeconds(process.hrtime(start))} seconds`); // Yep, I'm that fast... or slow 🐌 });

Using Async/Await for Accuracy & Readability

You can use async/await to measure execution time too, instead of relying on callbacks. Here's an example:

const measure = async (operation) => { const start = performance.now(); await operation(); const duration = performance.now() - start; console.log(`Time taken: ${duration} ms`); }; measure(asyncOperation);