Измерение времени выполнения функций — ценный метод анализа и оптимизации производительности кода.
Обычная история, когда нужно оценить время выполнения кода.
И первое, что приходит в голову — просто обернуть свой блок кода вот так:
const start = new Date().getTime(); // a very long code here const end = new Date().getTime(); const duration = end - start; console.log(`Execution time: ${duration} ms`);
И это совершенно нормально для отладки или одноразового использования. Но что, если у вас есть последовательность функций или несколько функций в приложении, которые вы хотите измерить?
Обертывание каждого вызова функции приведенным выше блоком кода может быстро загромождать вашу кодовую базу. Вместо этого лучше реализовать более универсальное решение, которое принимает вызовы функций в качестве аргументов и возвращает всю необходимую информацию. Именно здесь в игру вступают замыкания, обеспечивающие удобный способ инкапсуляции логики измерения.
const withTimer = (fnCall) => function(...args) { const start = new Date().getTime(); const result = fnCall.apply(this, args); const end = new Date().getTime(); return { start, end, duration: end - start, result }; }
И если вы хотите произвести впечатление на кого-то своими знаниями JS, вы можете сделать это так:
const withTimer = (fnCall) => function(...args) { const start = performance.now(); const result = fnCall.apply(this, args); const end = performance.now(); return { start, end, duration: end - start, result }; }
Функция-оболочка принимает единственный параметр, который является функцией, которую мы хотим измерить. Затем он возвращает новую функцию, которая выполняет измерение.
Вот некоторые примеры:
// 1. function myFunction(param1, param2) { // ... some code ... } const wrappedFunction = withTimer(myFunction); const { start, end, duration, result } = wrappedFunction(param1, param2); console.log(`Execution time: ${duration} ms`); console.log(`Actual function result: ${result}`); // 2. function slowFunction(obj) { // long operations } // let's avoid creating an intermediate function :) const result = withTimer(slowFunction)({ prop1: 'value1', prop2: 'value2' }); console.log(`Execution time: ${result.duration} milliseconds`); // 3. function doHeavyCalculation(a, b, c) { let result = 0; for (let i = 0; i < 1000000000; i++) { result += a * b * c; } return result; } const timedCalculation = withTimer(doHeavyCalculation); const result = timedCalculation(2, 3, 4); console.log(result.duration); // prints the execution time in milliseconds
Что еще более важно, так это то, что мы можем улучшить эту оболочку для обработки асинхронных функций.
const withTimerAsync = (fnCall) => async function() { const start = performance.now(); const result = await fnCall.apply(this, arguments); const end = performance.now(); return { start, end, duration: end - start, result }; }
Вы могли заметить, что я использовал несколько иной подход к работе с внутренними аргументами в асинхронной функции по сравнению с синхронной. Однако это различие не имеет значения, пока ваша внутренняя функция не является стрелочной функцией.
И вот еще несколько примеров использования:
// 1. async function myAsyncFunction(param1, param2) { // ... some code ... } const result = await withTimerAsync(myAsyncFunction)(param1, param2); // 2. async function fetchData(url) { const response = await fetch(url); return response.json(); } const timedFetchData = withTimerAsync(fetchData); const result = await timedFetchData('https://example.com/data.json'); console.log(result.duration); // prints the execution time in milliseconds
Наконец, стоит отметить, что эти две функции могут быть бесценны для выявления узких мест в вашем приложении, которые можно оптимизировать, не делая ваш код излишне запутанным.