Как отследить момент запуска и завершения обычной функции в JavaScript
Что проверяют
Умеете ли вы обернуть функцию, не меняя её код, и понимаете ли, что значит «завершилась» для синхронной и асинхронной функции. Следом просят сделать обёртку универсальной: сохранить this и аргументы, пережить исключение и корректно отработать промис.
Как отвечать
Базовый приём это функция высшего порядка:
function traced(fn, name = fn.name) {
return function (...args) {
const start = performance.now();
console.log(`${name} start`);
try {
return fn.apply(this, args);
} finally {
console.log(`${name} end`, performance.now() - start);
}
};
}Что здесь важно:
try/finally: конец фиксируется и при исключении, а само исключение не глотается;fn.apply(this, args): обёртка работает и для методов объекта, контекст не теряется;performance.now(): монотонное время с долями миллисекунды, в отличие отDate.now(), которое сдвигается при коррекции системных часов;- промисы: если
fnвернула промис,finallyсработает сразу, а не по окончании работы. Тогда нужно проверить результат и повесить отметку конца наresult.finally(...).
Для профилирования удобнее performance.mark() и performance.measure(): отрезки видны на таймлайне панели Performance в DevTools. Альтернативы обёртке: Proxy с ловушкой apply и декораторы, но декораторы применимы только к классам и их членам, к обычной функции их не повесить.
Где спотыкаются
Используют console.time('label') с одной и той же меткой. При параллельных или рекурсивных вызовах метки конфликтуют, браузер пишет предупреждение, а замер теряет смысл.
Замеряют асинхронную функцию синхронной обёрткой и получают доли миллисекунды вместо реальных секунд ожидания сети.
Пишут обёртку стрелочной функцией и вызывают fn(...args). Контекст вызывающего кода теряется, и методы, которые опираются на this, ломаются, причём не сразу заметно.