Почему верхнеуровневый await в блоке script setup может вызывать опасения?

Привет всем! Я использую Nuxt 3 и пытаюсь реализовать обработку ошибок при получении данных с API. В моем коде используется следующая структура: 

```html
<script setup>
const { data, status, error } = await useAsyncData('home', () => 
    $fetch('/api/getData', {
        params: { slug: '/' },
    })
);

if (error?.value) {
    console.error("Произошла ошибка:", error.value);
    throw createError({
        statusCode: error.value.statusCode || 500,
        statusMessage: error.value.message || 'Unknown error',
        fatal: true
    });
}
</script>
```

Я создала тестовую API по адресу /api/getData в server.api:

```javascript
export default defineEventHandler((event) => {
    // Получаем параметр `slug` из запроса
    const { slug } = getQuery(event);
  
    if (slug === '/') {
        return {
            constructor: [
                { id: 1, name: "Constructor 1", type: "Type A" },
                { id: 2, name: "Constructor 2", type: "Type B" },
            ],
            model: [
                { id: 1, name: "Model 1", category: "Category X" },
                { id: 2, name: "Model 2", category: "Category Y" },
            ],
        };
    }
  
    throw createError({
        statusCode: 404,
        statusMessage: "Invalid slug",
    });
});
```

Обработка ошибок работает корректно, когда я ставлю await перед useAsyncData, но, насколько я понимаю, это не является лучшей практикой. Я не до конца понимаю, какие последствия могут быть, если я буду использовать await в корне script setup. 

P.S. Буду очень признательна за примеры обработки ошибок без использования await.
  • 18 декабря 2024 г. 16:03
Ответы на вопрос 2
Использование верхнеуровневого `await` в блоке `<script setup>` может вызвать некоторые опасения, поскольку это может повлиять на поведение компонента и как он будет загружаться. Давайте рассмотрим несколько причин, почему это может быть не лучшей практикой:

1. **Синхронная загрузка**: Когда вы используете верхнеуровневый `await`, компонент будет ждать завершения асинхронной операции до того, как будет загружен. Это может привести к блокировке интерфейса и задержке отображения компонента, что, в свою очередь, ухудшит пользовательский опыт.

2. **Ошибки в структуре компонента**: Если в верхнем уровне `await` произошло исключение, это может привести к сбоям в загрузке самого компонента, вместо того чтобы обработать ошибку gracefully и продолжить с отображением компонентной структуры.

3. **Непредсказуемое поведение**: В некоторых ситуациях использование верхнего уровня `await` может привести к непредсказуемым результатам в компиляции и исполнении, особенно если вы работаете в среде, где существует асинхронная динамика загрузки компонентов или страниц.

Лучшей практикой является выполнять асинхронные операции внутри хуков или методов, которые могут обрабатывать ошибки более эффективно и управлять состоянием.

Вот пример, как можно обрабатывать ошибки без использования верхнеуровневого `await`:

```html
<script setup>
import { ref, onMounted } from 'vue';

const data = ref(null);
const error = ref(null);
const status = ref('loading'); // Можно трекать статус

async function fetchData() {
    try {
        const response = await useAsyncData('home', () => 
            $fetch('/api/getData', {
                params: { slug: '/' },
            })
        );

        data.value = response.data; // или useAsyncData, если нужно
        status.value = 'success'; // изменяем статус на успешный
    } catch (err) {
        console.error("Произошла ошибка:", err);
        error.value = err; // сохраняем ошибку в состоянии
        status.value = 'error'; // изменяем статус на ошибку
    }
}

// Вызываем fetchData при монтировании компонента
onMounted(() => {
    fetchData();
});
</script>

<template>
    <div>
        <div v-if="status === 'loading'">Загрузка...</div>
        <div v-if="status === 'error'">Произошла ошибка: {{ error.message }}</div>
        <div v-if="status === 'success'">
            <!-- отобразите данные здесь-->
            <pre>{{ data }}</pre>
        </div>
    </div>
</template>
```

В этом примере используется референс для хранения данных, ошибок и статуса загрузки. Функция `fetchData` вызывается в хуке `onMounted`, что позволяет избежать синхронной блокировки загрузки компонента. Ошибки обрабатываются внутри блока `try...catch`, что также позволяет избежать сбоев в загрузке самого компонента.
Если у вас версия vue >3.2, то страшного ничего нет. Проблема в том, что у vue рендер синхронный и после первого await в setup теряется контекст монтируемого компонента, который используется много где под капотом - реактивные функция вроде watch, хуки жизненного цикла и т.д. Это можно обойти, а начиная с vue 3.2 это сделано на уровне компилятора sfc. Подробно с примерами про это написано тут . 

Если говорить про useAsyncData - это уже композабл самого nuxt, нужный в первую очередь для дедупликации данных при ssr. По умолчанию данные запрашиваются на сервере, серилизуются и передаются вместе с html (пример на скриншоте). Этот композабл можно использовать и без await, при условии, что запрос вам нужно выполнять только на клиенте (server: false), тогда обработку ошибок надо будет делать через watch(error, ...)
Похожие вопросы