Измерение времени выполнения функций — ценный метод анализа и оптимизации производительности кода.

Обычная история, когда нужно оценить время выполнения кода.
И первое, что приходит в голову — просто обернуть свой блок кода вот так:

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

Наконец, стоит отметить, что эти две функции могут быть бесценны для выявления узких мест в вашем приложении, которые можно оптимизировать, не делая ваш код излишне запутанным.