Асинхронная обработка

Библиотека Marked предоставляет гибкие возможности для преобразования Markdown в HTML, включая поддержку асинхронной обработки. Асинхронная обработка особенно актуальна при работе с пользовательскими рендерерами, загрузкой внешних ресурсов или при необходимости выполнять операции, которые возвращают промисы.


Основы асинхронного парсинга

Marked поддерживает асинхронный режим через функцию marked.parse с опцией async: true. В этом режиме обработка Markdown может возвращать промис, что позволяет использовать await или then для получения результата:

import { marked } from 'marked';

const markdown = '# Заголовок\n\nНекоторый текст';

async function parseMarkdown() {
    const html = await marked.parse(markdown, { async: true });
    console.log(html);
}

parseMarkdown();

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


Асинхронные рендереры

Marked позволяет определить собственные рендереры через объект renderer. Каждый метод рендерера может быть асинхронным:

const renderer = {
    async link(href, title, text) {
        // Имитация асинхронной проверки URL
        await new Promise(resolve => setTimeout(resolve, 100));
        return `<a href="${href}" title="${title || ''}">${text}</a>`;
    }
};

const markdown = '[Google](https://google.com)';

marked.parse(markdown, { renderer, async: true }).then(html => {
    console.log(html);
});

Ключевой момент: если рендерер возвращает промис, нужно обязательно включать опцию async: true, иначе результат будет некорректным.


Асинхронная обработка токенов

Процесс преобразования Markdown в HTML проходит через лексический анализ (токенизацию) и рендеринг токенов. В асинхронном режиме возможна обработка каждого токена с использованием промисов:

import { Lexer, Parser } from 'marked';

const markdown = '![alt текст](https://example.com/image.png)';

async function asyncParse(markdown) {
    const lexer = new Lexer();
    const tokens = lexer.lex(markdown);

    const parser = new Parser({
        async image(token) {
            // Асинхронная загрузка изображения или проверка URL
            await new Promise(resolve => setTimeout(resolve, 50));
            return `<img src="${token.href}" alt="${token.text}">`;
        }
    });

    const html = await parser.parse(tokens, { async: true });
    console.log(html);
}

asyncParse(markdown);

Такой подход особенно полезен для встраивания данных из внешних источников, например, изображений, ссылок или динамического контента.


Асинхронные лексеры и кастомные токены

Marked позволяет создавать пользовательские токены и обрабатывать их асинхронно. Для этого используется объект tokenizer:

const tokenizer = {
    async myCustomToken(src) {
        const match = /^:::(.+?)\n([\s\S]+?)\n:::/m.exec(src);
        if (match) {
            await new Promise(resolve => setTimeout(resolve, 100));
            return {
                type: 'myCustomToken',
                raw: match[0],
                info: match[1],
                text: match[2]
            };
        }
    }
};

const renderer = {
    async myCustomToken(token) {
        return `<div class="custom">${token.text}</div>`;
    }
};

const markdown = `:::notice
Это важное сообщение
:::`;

marked.parse(markdown, { tokenizer, renderer, async: true }).then(html => {
    console.log(html);
});

Это демонстрирует, как можно расширять синтаксис Markdown и обрабатывать новые конструкции асинхронно.


Практические рекомендации

  1. Всегда включать async: true при использовании асинхронных рендереров или токенизаторов.
  2. Возвращать промисы из всех асинхронных методов рендерера.
  3. Не смешивать синхронный и асинхронный рендеринг для одного документа — это может вызвать непредсказуемое поведение.
  4. Для массовой обработки Markdown использовать Promise.all при генерации нескольких блоков, чтобы ускорить асинхронные операции.
  5. В случае ошибок в асинхронных рендерерах использовать стандартные механизмы try/catch или обработку .catch на промисах.

Асинхронные плагины

Marked допускает создание собственных плагинов для асинхронной обработки Markdown. Плагин может модифицировать токены, внедрять данные из API и изменять рендеринг:

function asyncPlugin() {
    return {
        async tokenizer(src) {
            const match = /^%%(.+?)%%/m.exec(src);
            if (match) {
                await new Promise(resolve => setTimeout(resolve, 50));
                return { type: 'asyncToken', raw: match[0], content: match[1] };
            }
        },
        async renderer(token) {
            if (token.type === 'asyncToken') {
                return `<span class="async">${token.content}</span>`;
            }
        }
    };
}

const { tokenizer, renderer } = asyncPlugin();

marked.parse('%%Асинхронный контент%%', { tokenizer, renderer, async: true }).then(html => {
    console.log(html);
});

Использование плагинов позволяет централизовать асинхронные преобразования и повторно применять их в разных проектах.


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