Урок 01

Fetch и axios в мобильном контексте

Fetch встроен

const res = await fetch('https://api.example.com/users');
const data = await res.json();

Работает так же, как в вебе. Но есть особенности мобильной платформы.

Axios

npm install axios
import axios from 'axios';

const { data } = await axios.get('/users');

Плюсы:

  • Автоматический JSON
  • Интерцепторы
  • Таймауты
  • Отмена запросов
  • Прогресс загрузки

Базовая настройка

// lib/api.ts
import axios from 'axios';
import * as SecureStore from 'expo-secure-store';

const api = axios.create({
    baseURL: process.env.EXPO_PUBLIC_API_URL,
    timeout: 10000,
    headers: {
        Accept: 'application/json',
        'Content-Type': 'application/json',
    },
});

api.interceptors.request.use(async (config) => {
    const token = await SecureStore.getItemAsync('token');
    if (token) {
        config.headers.Authorization = `Bearer ${token}`;
    }
    return config;
});

api.interceptors.response.use(
    (res) => res,
    async (error) => {
        if (error.response?.status === 401) {
            await SecureStore.deleteItemAsync('token');
            // навигация на логин
        }
        return Promise.reject(error);
    }
);

export default api;

Переменные окружения

В Expo — EXPO_PUBLIC_*:

EXPO_PUBLIC_API_URL=https://api.example.com
const url = process.env.EXPO_PUBLIC_API_URL;

Отмена запросов

При уходе с экрана:

useEffect(() => {
    const controller = new AbortController();

    async function load() {
        try {
            const res = await fetch(url, { signal: controller.signal });
            setData(await res.json());
        } catch (e) {
            if ((e as Error).name !== 'AbortError') setError(e as Error);
        }
    }

    load();
    return () => controller.abort();
}, [url]);

С axios:

const source = axios.CancelToken.source();
axios.get(url, { cancelToken: source.token });
return () => source.cancel();

Или AbortController (axios 1.x).

Если ты уже знаешь React

Fetch и axios работают одинаково. Отличия: EXPO_PUBLIC_* для env, SecureStore для токенов, AbortController обязателен.

Таймаут

У fetch нет таймаута. Решение:

async function fetchWithTimeout(url: string, ms: number) {
    const controller = new AbortController();
    const timeout = setTimeout(() => controller.abort(), ms);

    try {
        const res = await fetch(url, { signal: controller.signal });
        return res;
    } finally {
        clearTimeout(timeout);
    }
}

Retry

async function retry<T>(fn: () => Promise<T>, attempts = 3): Promise<T> {
    for (let i = 0; i < attempts; i++) {
        try {
            return await fn();
        } catch (e) {
            if (i === attempts - 1) throw e;
            await new Promise((r) => setTimeout(r, 1000 * (i + 1)));
        }
    }
    throw new Error('unreachable');
}

Итоги

  • Fetch встроен
  • Axios удобнее для больших проектов
  • Interceptors для токенов и ошибок
  • EXPO_PUBLIC_* для env
  • AbortController обязателен
  • SecureStore для токенов