Кастомные обработчики

Кастомные обработчики — это функции, которые берут на себя конкретную часть поведения: формирование строки, реакцию на ошибки, управление 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 'давно';
});

Кастомный обработчик обновления DOM

Вместо того чтобы полагаться на внутренние таймеры 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();

Обработчик с retry при ошибке

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
Трансформации Постобработка строки результата