Главная » Статьи » Как значительно улучшить fetch() с помощью шаблона декоратора

Как значительно улучшить fetch() с помощью шаблона декоратора

От автора: fetch() API позволяет выполнять сетевые запросы в веб-приложениях. Использование fetch() довольно простое: invoke fetch(‘/movies.json’) для запуска запроса. Когда запрос завершится, вы получите объект Response, из которого можете извлечь данные.

Вот простой пример того, как получить некоторые фильмы в формате JSON с URL /movies.json:

async function executeRequest() { const response = await fetch('/movies.json'); const moviesJson = await response.json(); console.log(moviesJson);
} executeRequest(); // logs [{ name: 'Heat' }, { name: 'Alien' }]

Как показано в приведенном выше фрагменте кода, вы должны вручную извлечь объект JSON из ответа: moviesJson = await response.json(). Сделать один раз — не проблема. Но если ваше приложение выполняет много запросов, извлечение всего, что использует объект JSON await response.json(), утомительно.

В результате заманчиво использовать стороннюю библиотеку, такую как axios, которая значительно упрощает обработку запросов. Рассмотрим такую же загрузку фильмов, используя axios:

async function executeRequest() { const moviesJson = await axios('/movies.json'); console.log(moviesJson);
} executeRequest(); // logs [{ name: 'Heat' }, { name: 'Alien' }]

moviesJson = await axios(‘/movies.json’) возвращает фактический ответ в формате JSON. Вам не нужно вручную извлекать JSON, как того требует fetch().

Но … использование вспомогательной библиотеки вроде axios порождает собственный набор проблем.

Во-первых, он увеличивает размер пакета веб-приложения. Во-вторых, приложение связано со сторонней библиотекой: вы получаете все преимущества этой библиотеки, но также получаете все ее ошибки.

Я применяю другой подход, в котором используется лучшее из обоих вариантов — использование шаблона декоратора для повышения простоты использования и гибкости fetch() API. Давайте посмотрим в следующих разделах, как это сделать.

Подготовка интерфейса

Шаблон декоратора полезен, потому что он позволяет добавлять функциональные возможности (другими словами — украшать) поверх базовой логики делая код гибким и слабосвязанным. Если вы не знакомы с шаблоном декоратора, я предлагаю прочитать, как он работает.

Применение декоратора для улучшения fetch() требует нескольких простых шагов. Первый шаг — объявить абстрактный интерфейс с именем Fetcher:

type ResponseWithData = Response & { data?: any }; interface Fetcher { run( input: RequestInfo, init?: RequestInit ): Promise<ResponseWithData>;
}

Fetcher интерфейс имеет только один метод, который принимает те же аргументы и возвращает тот же тип данных, что и обычный fetch(). Второй шаг — реализация базового класса:

class BasicFetcher implements Fetcher { run( input: RequestInfo, init?: RequestInit ): Promise<ResponseWithData> { return fetch(input, init); }
}

BasicFetcher реализует Fetcher интерфейс. Его единственный метод run() вызывает обычную функцию fetch() . Все просто. Давайте воспользуемся базовым классом для получения списка фильмов:

const fetcher = new BasicFetcher();const decoratedFetch = fetcher.run.bind(fetcher); async function executeRequest() { const response = await decoratedFetch('/movies.json'); const moviesJson = await response.json(); console.log(moviesJson);
} executeRequest(); // logs [{ name: 'Heat' }, { name: 'Alien' }]

Попробуйте демо

const fetcher = new BasicFetcher() /*создает экземпляр класса сборщика.*/
decoratedFetch = fetcher.run.bind(fetcher) /*создает связанный метод.*/

Затем вы можете использовать decoratedFetch(‘/movies.json’) для получения фильмов точно так же, как при использовании обычного fetch().

На этом этапе клас BasicFetcher не приносят пользы. Тем более что все усложняется из-за нового интерфейса и нового класса! Подождите немного… вы увидите огромные преимущества, когда декораторы будут задействованы в действии.

Декоратор экстрактора JSON

Рабочей лошадкой паттерна декоратора является класс декоратора. Класс декоратора должен соответствовать Fetcherи нтерфейсу, оборачивать экземпляр, а также вводить дополнительные функции в метод run().

Давайте реализуем декоратор, который извлекает данные JSON из объекта ответа:

class JsonFetcherDecorator implements Fetcher { private decoratee: Fetcher; constructor (decoratee: Fetcher) { this.decoratee = decoratee; } async run( input: RequestInfo, init?: RequestInit ): Promise<ResponseWithData> { const response = await this.decoratee.run(input, init); const json = await response.json(); response.data = json; return response; }
}

Давайте подробнее разберемся, как устроен JsonFetcherDecorator. JsonFetcherDecorator реализует интерфейс Fetcher. JsonExtractorFetch имеет закрытое поле decorateе которое также соответствует Fetcher интерфейсу. Внутри метода run() происходит фактическая выборка данных this.decoratee.run(input, init).

Затем json = await response.json() извлекает данные JSON из ответа. Наконец, response.data = json присваивает объекту ответа извлеченные данные JSON. Теперь давайте скомпонуем BasicFetcher с помощью JsonFetcherDecorator декоратора и упростим использование fetch():

const fetcher = new JsonFetcherDecorator( new BasicFetcher());const decoratedFetch = fetcher.run.bind(fetcher); async function executeRequest() { const { data } = await decoratedFetch('/movies.json'); console.log(data);
} executeRequest(); // logs [{ name: 'Heat' }, { name: 'Alien' }]

Попробуйте демо

Теперь вместо извлечения вручную данных JSON из ответа вы можете получить доступ к извлеченным данным из dataс войства объекта ответа.

Переместив экстрактор JSON в декоратор, теперь в любом месте, которое вы используете, const { data } = decoratedFetch(URL) вам не придется вручную извлекать объект JSON.

Декоратор тайм-аута запроса

fetch() API по умолчанию отключает запросы во время, указанное браузером. В Chrome тайм-аут сетевого запроса составляет 300 секунд, а в Firefox — 90 секунд.

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

Что замечательно в шаблоне декоратора, так это то, что вы можете расширить свою базовую реализацию любым количеством декораторов! Итак, давайте создадим декоратор тайм-аута для запросов на выборку:

const TIMEOUT = 8000; // 8 seconds class TimeoutFetcherDecorator implements Fetcher { private decoratee: Fetcher; constructor(decoratee: Fetcher) { this.decoratee = decoratee; } async run( input: RequestInfo, init?: RequestInit ): Promise<ResponseWithData> { const controller = new AbortController(); const id = setTimeout(() => controller.abort(), TIMEOUT); const response = await this.decoratee.run(input, { ...init, signal: controller.signal }); clearTimeout(id); return response; }
}

TimeoutFetcherDecorator — декоратор, реализующий Fetcher интерфейс. Внутри run() метода контроллер прерывания используется для прерывания запроса, если он не был завершен в течение 8 секунд.

Теперь давайте запустим этот декоратор:

const fetcher = new TimeoutFetcherDecorator( new JsonFetcherDecorator( new BasicFetcher() )
);
const decoratedFetch = fetcher.run.bind(fetcher); async function executeRequest() { try { const { data } = await decoratedFetch('/movies.json'); console.log(data); } catch (error) { // Timeouts if the request takes // longer than 8 seconds console.log(error.name); }
} executeRequest(); // if the request takes more than 8 seconds
// logs "AbortError"

Попробуйте демо

В демо запрос /movies.json занимает более 8 секунд. decoratedFetch(‘/movies.json’), благодаря TimeoutFetcherDecorator, выдает ошибку тайм-аута.

Теперь базовый сборщик обернут в 2 декоратора: один извлекает объект JSON, а другой тайм-аут запроса составляет 8 секунд. Это значительно упрощает использование decoratedFetch(): при вызове decoratedFetch() декораторов логика работает за вас.

Заключение

fetch() API предоставляет базовые функции для выполнения запросов на выборку. Но вам нужно больше! Использование fetch() только заставляет вас вручную извлекать данные JSON из запроса, настраивать тайм-аут и многое другое.

Чтобы избежать шаблонов, вы можете использовать более удобную библиотеку, например axios. Однако использование сторонней библиотеки, например, axios увеличивает размер пакета приложений, а также вы тесно связаны с ним.

Альтернативное решение — использовать декоратор поверх fetch(). Вы можете создавать декораторы, которые извлекают JSON из запроса, задерживают запрос и многое другое. Вы можете комбинировать, добавлять или удалять декораторы в любое время, не затрагивая код, который использует декорированную выборку.

Хотели бы вы использовать fetch() с наиболее распространенными декораторами? Я создал для вас gist! Не стесняйтесь использовать его в своем приложении или даже добавлять декораторы для своих нужд!

Автор: Dmitri Pavlutin

Источник: dmitripavlutin.com

Редакция: Команда webformyself.