Кастомные обработчики — это функции, которые берут на себя конкретную часть поведения: формирование строки, реакцию на ошибки, управление DOM-обновлениями. Они встраиваются в поток обработки без замены всей библиотеки.
import { format } from 'timeago.js';
type DateInput = Date | string | number;
type FormatResult = string;
type FormatHandler = (date: DateInput, locale: string) => FormatResult;
function createFormatHandler(overrides?: {
onNull?: (locale: string) => string;
onInvalid?: (date: unknown, locale: string) => string;
onFuture?: (date: DateInput, locale: string) => string;
}): FormatHandler {
return (date, locale) => {
if (date == null) {
return overrides?.onNull?.(locale) ?? '';
}
const d = new Date(date as any);
if (isNaN(d.getTime())) {
return overrides?.onInvalid?.(date, locale) ?? '';
}
if (d.getTime() > Date.now() && overrides?.onFuture) {
return overrides.onFuture(date, locale);
}
return format(d, locale);
};
}
const handler = createFormatHandler({
onNull: () => 'Дата не указана',
onInvalid: (v) => `Некорректная дата: ${v}`,
onFuture: (d, l) => `В будущем: ${format(d, l)}`,
});
import { format } from 'timeago.js';
type ErrorHandler = (err: Error, date: unknown, locale: string) => string;
function withErrorHandler(
handler: ErrorHandler,
fallback = 'давно'
): (date: unknown, locale?: string) => string {
return (date, locale = 'ru') => {
try {
if (date == null) throw new TypeError('date is null');
const d = new Date(date as any);
if (isNaN(d.getTime())) throw new TypeError(`Invalid date: ${date}`);
return format(d, locale);
} catch (err) {
return handler(err as Error, date, locale);
}
};
}
const safeFormat = withErrorHandler((err, date, locale) => {
console.error(`[timeago error] ${err.message}`, { date, locale });
return 'давно';
});
Вместо того чтобы полагаться на внутренние таймеры timeago.js, можно реализовать собственный цикл обновления:
import { format } from 'timeago.js';
type UpdateHandler = (el: HTMLTimeElement, newText: string, prevText: string) => void;
function createDOMUpdater(handler?: UpdateHandler) {
return {
upd ate(el: HTMLTimeElement, locale = 'ru'): void {
const datetime = el.getAttribute('datetime');
if (!datetime) return;
const newText = format(datetime, locale);
const prevText = el.textContent ?? '';
if (newText !== prevText) {
if (handler) {
handler(el, newText, prevText);
} else {
el.textContent = newText;
}
}
},
updateAll(selector = '[datetime]', locale = 'ru'): void {
document.querySelectorAll<HTMLTimeElement>(selector)
.forEach(el => this.update(el, locale));
},
};
}
// Кастомный DOM-обработчик с анимацией
const updater = createDOMUpdater((el, newText, prevText) => {
el.style.opacity = '0';
setTimeout(() => {
el.textContent = newText;
el.style.opacity = '1';
}, 150);
});
На сервере нет DOM, поэтому render недоступен. Кастомный
обработчик адаптируется к среде:
import { format } from 'timeago.js';
const isServer = typeof window === 'undefined';
function universalFormat(date: Date | string | number, locale = 'ru'): string {
try {
return format(date, locale);
} catch {
return new Date(date as any).toLocaleDateString('ru-RU');
}
}
function universalRender(
el: Element | null,
locale = 'ru',
onMount: (el: Element) => void
): void {
if (isServer || !el) return;
// Установить начальный текст
if (el instanceof HTMLTimeElement) {
const datetime = el.getAttribute('datetime');
if (datetime) el.textContent = universalFormat(datetime, locale);
}
// Вызвать колбэк после монтирования
onMount(el);
}
import { format } from 'timeago.js';
class UpdateQueue {
private queue: Map<string, { el: HTMLTimeElement; locale: string }> = new Map();
private pending = false;
add(el: HTMLTimeElement, locale = 'ru'): void {
const key = el.getAttribute('datetime') ?? String(Math.random());
this.queue.se t(key, { el, locale });
if (!this.pending) {
this.pending = true;
requestAnimationFrame(() => this.flush());
}
}
private flush(): void {
this.queue.forEach(({ el, locale }) => {
const datetime = el.getAttribute('datetime');
if (!datetime) return;
const newText = format(datetime, locale);
if (el.textContent !== newText) {
el.textContent = newText;
}
});
this.queue.clear();
this.pending = false;
}
}
const queue = new UpdateQueue();
import { format } from 'timeago.js';
async function formatWithRetry(
date: Date | string | number,
locale: string,
retries = 3,
delay = 100
): Promise<string> {
for (let attempt = 0; attempt < retries; attempt++) {
try {
return format(date, locale);
} catch (err) {
if (attempt === retries - 1) throw err;
await new Promise(r => setTimeout(r, delay * (attempt + 1)));
}
}
return '';
}
import { format } from 'timeago.js';
type ResultTransformer = (result: string, date: Date | string | number, locale: string) => string;
function withTransform(transformer: ResultTransformer) {
return (date: Date | string | number, locale = 'ru'): string => {
const result = format(date, locale);
return transformer(result, date, locale);
};
}
// Заглавная первая буква
const capitalizedFormat = withTransform((result) =>
result.charAt(0).toUpperCase() + result.slice(1)
);
// Добавить дату в скобках
const verboseFormat = withTransform((result, date) => {
const d = new Date(date as any);
return `${result} (${d.toLocaleDateString('ru-RU')})`;
});
| Обработчик | Назначение |
|---|---|
| Форматирования | Изменить логику построения строки |
| Ошибок | Возвращать fallback при исключениях |
| DOM-обновлений | Контролировать как именно изменяется DOM |
| SSR | Адаптировать поведение к серверной среде |
| Очереди | Батчить обновления через requestAnimationFrame |
| Трансформации | Постобработка строки результата |