Асинхронное выполнение кода является фундаментальной частью современного JavaScript. Оно позволяет не блокировать основной поток исполнения, обеспечивая отзывчивость интерфейсов и эффективное взаимодействие с внешними ресурсами. В React асинхронность особенно важна при работе с данными, сетевыми запросами и событиями, требующими отложенного выполнения.
Промис — объект, представляющий результат асинхронной операции, который может быть в состоянии pending, fulfilled или rejected. Основные методы работы с промисами:
fetch('/api/data')
.then(response => response.json())
.then(data => console.log(data))
.catch(error => console.error(error));
async/await позволяет писать асинхронный код в
синхронном стиле, улучшая читаемость:
async function fetchData() {
try {
const response = await fetch('/api/data');
const data = await response.json();
console.log(data);
} catch (error) {
console.error(error);
}
}
В контексте React async/await часто используется внутри
эффектов:
useEffect(() => {
async function loadData() {
const response = await fetch('/api/data');
const result = await response.json();
setData(result);
}
loadData();
}, []);
React Testing Library ориентирована на тестирование компонентов так, как они используются пользователями, что подразумевает работу с асинхронными эффектами, сетевыми запросами и событиями интерфейса.
waitFor — ожидание определенного
состояния компонента:import { render, screen, waitFor } from '@testing-library/react';
import MyComponent from './MyComponent';
test('загрузка данных и отображение', async () => {
render(<MyComponent />);
await waitFor(() => expect(screen.getByText('Данные загружены')).toBeInTheDocument());
});
waitFor повторяет проверку до тех пор, пока условие не
выполнится или не истечет таймаут.
findBy — сокращенная форма ожидания
элемента:const element = await screen.findByText('Данные загружены');
expect(element).toBeInTheDocument();
findBy автоматически использует внутренний
waitFor, поэтому не требуется дополнительная обертка.
import userEvent from '@testing-library/user-event';
test('нажатие кнопки вызывает загрузку данных', async () => {
render(<MyComponent />);
const button = screen.getByRole('button', { name: /загрузить/i });
await userEvent.click(button);
expect(await screen.findByText('Данные загружены')).toBeInTheDocument();
});
userEvent.click может быть асинхронным, если вызывает
setState или эффект, поэтому используется
await.
msw или jest.fn() для создания моков:global.fetch = jest.fn(() =>
Promise.resolve({ json: () => Promise.resolve({ message: 'OK' }) })
);
Обновления состояния в React выполняются
асинхронно. Проверки нужно делать после завершения обновлений, используя
waitFor или findBy.
Таймауты и ошибка ожидания: по умолчанию
waitFor и findBy ждут до 1000 мс. Можно
изменить через параметр { timeout: 3000 }.
Сброс моков и состояний между тестами:
afterEach(() => {
jest.clearAllMocks();
});
Это предотвращает взаимное влияние тестов и обеспечивает независимость.
React-хуки часто используются с асинхронным кодом:
useEffect — для загрузки данных после
рендера.useState — хранение результатов
асинхронных операций.useReducer — управление сложными
состояниями с различными статусами (loading,
success, error).Пример использования useReducer для асинхронной
загрузки:
function dataReducer(state, action) {
switch (action.type) {
case 'LOADING':
return { ...state, loading: true };
case 'SUCCESS':
return { loading: false, data: action.payload, error: null };
case 'ERROR':
return { loading: false, data: null, error: action.payload };
default:
return state;
}
}
function MyComponent() {
const [state, dispatch] = useReducer(dataReducer, { loading: false, data: null, error: null });
useEffect(() => {
async function fetchData() {
dispatch({ type: 'LOADING' });
try {
const response = await fetch('/api/data');
const data = await response.json();
dispatch({ type: 'SUCCESS', payload: data });
} catch (error) {
dispatch({ type: 'ERROR', payload: error });
}
}
fetchData();
}, []);
if (state.loading) return <div>Загрузка...</div>;
if (state.error) return <div>Ошибка!</div>;
return <div>{state.data?.message}</div>;
}
Тестирование такого компонента:
test('успешная загрузка данных', async () => {
render(<MyComponent />);
expect(screen.getByText('Загрузка...')).toBeInTheDocument();
expect(await screen.findByText('OK')).toBeInTheDocument();
});
setState.act() вручную,
если применяются findBy или waitFor, так как
RTL автоматически оборачивает асинхронные операции в
act().Асинхронность в React тесно связана с обновлением состояния и
эффектами. Понимание того, как правильно ожидать изменения DOM,
использовать waitFor и findBy, а также
мокировать внешние запросы, обеспечивает стабильные и предсказуемые
тесты, которые отражают поведение приложения в реальном времени.