Практика: утилиты на функциях
Собрали набор утилит, которые часто нужны в реальных проектах.
debounce
Откладывает вызов до паузы:
function debounce(fn, delay) {
let timer = null;
return function(...args) {
clearTimeout(timer);
timer = setTimeout(() => fn.apply(this, args), delay);
};
}
const onSearch = debounce((q) => fetchResults(q), 300);
throttle
Не чаще раза в интервал:
function throttle(fn, delay) {
let last = 0;
let timer = null;
return function(...args) {
const now = Date.now();
const remaining = delay - (now - last);
if (remaining <= 0) {
last = now;
fn.apply(this, args);
} else if (!timer) {
timer = setTimeout(() => {
last = Date.now();
timer = null;
fn.apply(this, args);
}, remaining);
}
};
}
once
function once(fn) {
let called = false;
let result;
return function(...args) {
if (!called) {
called = true;
result = fn.apply(this, args);
}
return result;
};
}
memoize
function memoize(fn, keyFn = JSON.stringify) {
const cache = new Map();
return function(...args) {
const key = keyFn(args);
if (cache.has(key)) return cache.get(key);
const result = fn.apply(this, args);
cache.set(key, result);
return result;
};
}
curry
function curry(fn) {
return function curried(...args) {
if (args.length >= fn.length) return fn.apply(this, args);
return (...next) => curried.apply(this, [...args, ...next]);
};
}
compose
const compose = (...fns) => (x) => fns.reduceRight((acc, fn) => fn(acc), x);
const pipe = (...fns) => (x) => fns.reduce((acc, fn) => fn(acc), x);
retry
async function retry(fn, { attempts = 3, delay = 1000, backoff = 2 } = {}) {
let wait = delay;
for (let i = 0; i < attempts; i++) {
try {
return await fn();
} catch (error) {
if (i === attempts - 1) throw error;
await new Promise((r) => setTimeout(r, wait));
wait *= backoff;
}
}
}
timeout
function withTimeout(promise, ms) {
return Promise.race([
promise,
new Promise((_, reject) =>
setTimeout(() => reject(new Error('Timeout')), ms)
),
]);
}
sleep
const sleep = (ms) => new Promise((r) => setTimeout(r, ms));
await sleep(1000);
chunk
function chunk(arr, size) {
const result = [];
for (let i = 0; i < arr.length; i += size) {
result.push(arr.slice(i, i + size));
}
return result;
}
groupBy
function groupBy(items, keyFn) {
return items.reduce((acc, item) => {
const key = typeof keyFn === 'function' ? keyFn(item) : item[keyFn];
acc[key] = acc[key] ?? [];
acc[key].push(item);
return acc;
}, {});
}
uniqueBy
function uniqueBy(items, keyFn) {
const seen = new Set();
return items.filter((item) => {
const key = typeof keyFn === 'function' ? keyFn(item) : item[keyFn];
if (seen.has(key)) return false;
seen.add(key);
return true;
});
}
sortBy
function sortBy(items, keyFn, direction = 'asc') {
return [...items].sort((a, b) => {
const av = typeof keyFn === 'function' ? keyFn(a) : a[keyFn];
const bv = typeof keyFn === 'function' ? keyFn(b) : b[keyFn];
if (av < bv) return direction === 'asc' ? -1 : 1;
if (av > bv) return direction === 'asc' ? 1 : -1;
return 0;
});
}
Итоги
debounce,throttle— для событийonce,memoize,curry— для оптимизацииcompose,pipe— для композицииretry,withTimeout,sleep— для асинхронностиchunk,groupBy,uniqueBy,sortBy— для массивов- Такие утилиты удобно выносить в
utils/