На днях я искал решение, работая над своим приложением Vue. У меня было несколько компонентов, которые использовали одни и те же данные из API и при совместном использовании отправляли повторяющиеся запросы к одной и той же конечной точке. Немного поразмыслив и поэкспериментировав, я собрал решение, которое, по моему мнению, может оказаться полезным для других, столкнувшихся с аналогичной проблемой.

В этой статье я опишу сценарий, объясню проблемы и расскажу вам о коде, который помог мне предотвратить дублирование вызовов API. Независимо от того, столкнулись ли вы с этой проблемой самостоятельно или просто хотите создать свой набор инструментов Vue, цель этого руководства — предоставить практическое решение этой распространенной проблемы.

Сценарий

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

Этот поток одновременных запросов может привести к нескольким проблемам:

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

Соревнование

Когда мы столкнулись с несколькими компонентами, использующими одни и те же данные API, возникли две основные проблемы, требующие тщательного рассмотрения:

  • Включение независимых компонентов. Каждый компонент должен иметь возможность включать и загружать данные независимо, не полагаясь на родительский компонент для получения необходимой информации. Если компоненты используются независимо без родительского компонента, содержащего логику загрузки, они не смогут получить необходимые данные. Это означает, что ответственность за выборку данных должна лежать внутри самих компонентов, что позволяет им правильно функционировать независимо от того, используются ли они отдельно или вместе с другими.
  • Избежание дублирования запросов. При одновременном использовании нескольких компонентов необходимо предотвращать дублирование запросов API. Хотя существует несколько способов справиться с этим, я хотел, чтобы первый запрос продолжался и был успешным, а все последующие запросы, пока первый все еще находится на рассмотрении, просто ждали завершения первоначального запроса. Это гарантирует, что система не тратит ресурсы на избыточные запросы одних и тех же данных, а также помогает поддерживать согласованность, поскольку все компоненты, использующие эти данные, получат одну и ту же информацию после выполнения первоначального запроса.

Решение

Вот фрагмент кода, который решает эту проблему с использованием хранилища настроек Pinia с Composition API (как я предпочитаю):

import { defineStore } from 'pinia';
import { ref } from 'vue';
import apiClient from 'boot/axios';

export const useProfileStore = defineStore('profile', () => {

    const profile = ref(null);

    let loadProfileRequest = null;

    const loadProfile = async () => {
        if (loadProfileRequest) {
            return loadProfileRequest;
        }
        loadProfileRequest = new Promise(async (resolve, reject) => {
            try {
                profile.value = (await apiClient.get('/v1/user/profile')).data;
                resolve();
            }
            catch (err) {
                reject(err);
            }
            finally {
                loadProfileRequest = null;
            }
        });
        return loadProfileRequest;
    };

    return {
        profile,
        loadProfile
    };
});

Как работает код

  1. Инициализация. Фрагмент кода определяет хранилище с именем «профиль», которое включает в себя реактивную ссылку profile для хранения данных профиля пользователя и переменную loadProfileRequest для отслеживания текущего запроса API.
  2. Проверка текущего запроса. Внутри функции loadProfile проверяется, хранится ли текущий запрос в loadProfileRequest. Если это так, он возвращает тот же запрос, позволяя другим компонентам дождаться его завершения, а не инициировать новый.
  3. Создание и сохранение запроса. Если запрос не выполняется, для запроса API создается новое обещание, а ссылка сохраняется в loadProfileRequest. Любые последующие вызовы loadProfile из других компонентов будут возвращать этот запрос вместо того, чтобы инициировать новый.
  4. Выполнение вызова API: внутри обещания выполняется фактический вызов API, и результат присваивается profile.value.
  5. Разрешение или отклонение обещания. В зависимости от успеха или неудачи вызова API обещание либо разрешается (в данном случае без значения), либо отклоняется с ошибкой.
  6. Очистка. В блоке finally сбрасывается переменная текущего запроса. Это гарантирует, что последующие вызовы loadProfile при необходимости создадут новый запрос.
  7. Обновление компонентов. После завершения запроса API реактивная ссылка profile будет обновлена ​​данными. В результате обновленные данные станут доступны внутри компонентов, использующих это хранилище. Нет необходимости в отдельных компонентах для управления запросом или его состоянием; они могут просто использовать значение profile по мере его обновления.

Этот шаблон обеспечивает гибкий и эффективный способ управления общими данными API для нескольких компонентов Vue. Централизуя логику загрузки и тщательно управляя текущими запросами, вы можете гарантировать, что компоненты будут самодостаточны в получении необходимых им данных, а также предотвратите их избыточный запрос одних и тех же данных при совместном использовании.

Используя систему реактивности Vue, компоненты могут просто наблюдать за общим значением profile и реагировать на обновления, не беспокоясь о базовом механизме загрузки. Такая инкапсуляция проблем помогает создать чистую и поддерживаемую базу кода.

Благодарим вас за чтение и надеюсь, что это решение оказалось для вас полезным.

У вас есть другое решение той же проблемы, которое мы можем добавить в набор инструментов Vue? Напишите в комментариях!

Приятного кодирования!