Обработка асинхронных операций

Асинхронные операции в JavaScript — это операции, выполнение которых занимает время и не блокирует основной поток исполнения кода. В контексте Riot.js асинхронность особенно важна при работе с API, загрузке данных с сервера, таймерами или любыми другими задержками. Riot.js предоставляет гибкие механизмы для интеграции асинхронного кода с реактивными компонентами.


Использование async/await внутри компонентов

Riot.js позволяет определять методы компонентов, в которых можно применять синтаксис async/await. Это упрощает работу с промисами и делает код более читаемым.

<user-list>
  <ul>
    <li each={user in users}>{user.name}</li>
  </ul>

  <script>
    import { onMounted } from 'riot';

    export default {
      users: [],

      async fetchUsers() {
        try {
          const response = await fetch('https://jsonplaceholder.typicode.com/users');
          this.users = await response.json();
        } catch (error) {
          console.error('Ошибка загрузки пользователей:', error);
        }
      },

      onMounted() {
        this.fetchUsers();
      }
    }
  </script>
</user-list>

Ключевые моменты:

  • Метод fetchUsers объявлен как async, что позволяет использовать await для промисов.
  • Состояние компонента (users) автоматически обновляет отображение при присвоении нового значения.
  • В Riot.js методы жизненного цикла, такие как onMounted, удобно использовать для запуска асинхронных операций после монтирования компонента.

Работа с промисами

Для обработки промисов можно использовать стандартные методы then и catch. Этот подход полезен, если не требуется синтаксис async/await.

<posts-list>
  <ul>
    <li each={post in posts}>{post.title}</li>
  </ul>

  <script>
    export default {
      posts: [],

      fetchPosts() {
        fetch('https://jsonplaceholder.typicode.com/posts')
          .then(res => res.json())
          .then(data => this.posts = data)
          .catch(err => console.error('Ошибка загрузки постов:', err));
      },

      onMounted() {
        this.fetchPosts();
      }
    }
  </script>
</posts-list>

Особенности подхода с then:

  • Позволяет цепочечную обработку асинхронных вызовов.
  • Более явное управление обработкой ошибок через catch.
  • Подходит для совместимости с более старыми версиями браузеров или кодовой базы без async/await.

Интеграция с реактивным состоянием

Riot.js использует реактивное обновление DOM на основе изменений состояния компонентов. Асинхронные операции напрямую взаимодействуют с этим механизмом:

  • Любое изменение свойства компонента автоматически инициирует ререндер соответствующей части шаблона.
  • Асинхронные методы можно комбинировать с реактивными вычисляемыми свойствами для динамического обновления интерфейса.
<todo-app>
  <ul>
    <li each={item INTOdos}>{item.task} - {item.completed ? 'Выполнено' : 'В процессе'}</li>
  </ul>

  <script>
    export default {
      todos: [],

      async loadTodos() {
        const res = await fetch('/api/todos');
        this.todos = await res.json();
      },

      toggleTodoCompletion(todo) {
        todo.completed = !todo.completed;
      },

      onMounted() {
        this.loadTodos();
      }
    }
  </script>
</todo-app>

Пояснения:

  • Метод toggleTodoCompletion мгновенно обновляет интерфейс без дополнительных вызовов ререндеринга.
  • Асинхронная загрузка через loadTodos интегрирована с реактивным массивом todos.

Обработка нескольких асинхронных операций

Часто требуется выполнять несколько асинхронных задач одновременно и обрабатывать результаты после завершения всех операций. Для этого используется Promise.all или Promise.allSettled.

<dashboard>
  <div>
    <h3>Данные пользователей</h3>
    <pre>{JSON.stringify(users)}</pre>

    <h3>Статистика постов</h3>
    <pre>{JSON.stringify(posts)}</pre>
  </div>

  <script>
    export default {
      users: [],
      posts: [],

      async loadData() {
        try {
          const [usersData, postsData] = await Promise.all([
            fetch('/api/users').then(res => res.json()),
            fetch('/api/posts').then(res => res.json())
          ]);

          this.users = usersData;
          this.posts = postsData;
        } catch (error) {
          console.error('Ошибка загрузки данных:', error);
        }
      },

      onMounted() {
        this.loadData();
      }
    }
  </script>
</dashboard>

Особенности использования Promise.all:

  • Асинхронные операции выполняются параллельно.
  • Если хотя бы один промис завершится ошибкой, Promise.all отклоняется.
  • Для обработки ошибок каждого промиса отдельно можно использовать Promise.allSettled.

Асинхронные события и пользовательский интерфейс

Riot.js позволяет асинхронно обрабатывать события пользователя, сохраняя реактивность интерфейса:

<search-bar>
  <input onin put={handleInput} placeholder="Поиск..." />
  <ul>
    <li each={result in results}>{result.name}</li>
  </ul>

  <script>
    export default {
      results: [],

      async handleInput(event) {
        const query = event.target.value;
        if (!query) {
          this.results = [];
          return;
        }

        try {
          const res = await fetch(`/api/search?q=${encodeURIComponent(query)}`);
          this.results = await res.json();
        } catch (err) {
          console.error('Ошибка поиска:', err);
        }
      }
    }
  </script>
</search-bar>
  • Асинхронная обработка событий позволяет выполнять запросы без блокировки ввода.
  • Реактивное обновление массива results автоматически отображает новые данные в DOM.

Ловушки и особенности асинхронного кода в Riot.js

  1. Контекст this: Внутри асинхронных функций this сохраняется, если используется синтаксис методов объекта. При использовании обычных функций или стрелочных функций нужно следить за контекстом.
  2. Отмена операций: При переходе на другую страницу или удалении компонента рекомендуется учитывать отмену долгих запросов, чтобы избежать утечек памяти.
  3. Обработка ошибок: Важно всегда оборачивать await или промисы в блоки try/catch, чтобы не возникали необработанные исключения, которые могут нарушить реактивность компонента.
  4. Параллельные vs последовательные операции: Последовательное выполнение через await в цикле блокирует поток, тогда как Promise.all ускоряет параллельную загрузку данных.

Асинхронность в Riot.js интегрирована с реактивностью компонентов, что позволяет создавать динамичные и отзывчивые интерфейсы. Использование async/await, промисов и методов жизненного цикла обеспечивает плавное взаимодействие с внешними API и динамическими данными, сохраняя чистоту и читаемость кода.