Другое

Полное руководство: AJAX-запросы без jQuery для начинающих

Освойте AJAX на JavaScript с руководством по XMLHttpRequest и Fetch API. Узнайте лучшие практики, обработку ошибок и способы оптимизации производительности.

Как можно выполнить AJAX‑запрос, используя чистый JavaScript без jQuery? Какие существуют методы и лучшие практики реализации AJAX‑функционала в ванильном JavaScript?

Вы можете выполнять AJAX‑запросы с помощью чистого JavaScript без jQuery, используя либо объект XMLHttpRequest для обратной совместимости, либо современный API Fetch. Оба подхода имеют свои преимущества: XMLHttpRequest обеспечивает более широкую совместимость и более детальный контроль, а Fetch предлагает более чистый синтаксис с обработкой на основе промисов, что делает код более читаемым и поддерживаемым.

Содержание


XMLHttpRequest: традиционный подход

Объект XMLHttpRequest является традиционным способом реализации AJAX‑функциональности в JavaScript и остаётся широко поддерживаемым во всех браузерах. Как объясняет Mozilla Developer Network, «XMLHttpRequest представляет собой простой способ получить данные с URL без полной перезагрузки страницы».

Базовая реализация XMLHttpRequest

Ниже приведён простой GET‑запрос с использованием XMLHttpRequest:

javascript
const xhr = new XMLHttpRequest();
xhr.open('GET', 'https://api.example.com/data', true);
xhr.onload = function() {
    if (xhr.status >= 200 && xhr.status < 300) {
        const data = JSON.parse(xhr.responseText);
        console.log('Success:', data);
    } else {
        console.error('Request failed with status:', xhr.status);
    }
};
xhr.onerror = function() {
    console.error('Network error occurred');
};
xhr.send();

XMLHttpRequest для разных HTTP‑методов

POST‑запросы с данными:

javascript
const xhr = new XMLHttpRequest();
xhr.open('POST', 'https://api.example.com/submit', true);
xhr.setRequestHeader('Content-Type', 'application/json');
xhr.onload = function() {
    if (xhr.status >= 200 && xhr.status < 300) {
        const response = JSON.parse(xhr.responseText);
        console.log('Submission successful:', response);
    }
};
xhr.send(JSON.stringify({ name: 'John', email: 'john@example.com' }));

Отслеживание прогресса:

Согласно руководству SitePoint, вы можете отслеживать прогресс AJAX‑запроса с помощью обработчика события onprogress:

javascript
const xhr = new XMLHttpRequest();
xhr.open('GET', 'https://api.example.com/large-file', true);
xhr.onprogress = function(event) {
    if (event.lengthComputable) {
        const percentComplete = (event.loaded / event.total) * 100;
        console.log('Download progress: ' + percentComplete + '%');
    }
};
xhr.send();

Fetch API: современное решение

API Fetch, введённый с ES6 (ECMAScript 2015), предоставляет более современный и упрощённый подход к выполнению AJAX‑запросов. Как отмечает Go Make Things, «Fetch полностью поддерживается в Deno и Node 18, позволяя использовать один и тот же API как на сервере, так и на клиенте».

Базовая реализация Fetch

javascript
fetch('https://api.example.com/data')
    .then(response => {
        if (!response.ok) {
            throw new Error('Network response was not ok');
        }
        return response.json();
    })
    .then(data => {
        console.log('Success:', data);
    })
    .catch(error => {
        console.error('Fetch error:', error);
    });

Fetch с async/await

javascript
async function fetchData() {
    try {
        const response = await fetch('https://api.example.com/data');
        if (!response.ok) {
            throw new Error(`HTTP error! status: ${response.status}`);
        }
        const data = await response.json();
        console.log('Success:', data);
    } catch (error) {
        console.error('Fetch error:', error);
    }
}
fetchData();

POST‑запрос с Fetch

javascript
async function postData() {
    try {
        const response = await fetch('https://api.example.com/submit', {
            method: 'POST',
            headers: {
                'Content-Type': 'application/json',
            },
            body: JSON.stringify({
                name: 'John',
                email: 'john@example.com'
            })
        });
        
        if (!response.ok) {
            throw new Error(`HTTP error! status: ${response.status}`);
        }
        
        const result = await response.json();
        console.log('Submission successful:', result);
    } catch (error) {
        console.error('Fetch error:', error);
    }
}

Лучшие практики для Vanilla AJAX

1. Всегда обрабатывайте ошибки

Для XMLHttpRequest:

javascript
function makeRequest(url, method = 'GET', data = null) {
    return new Promise((resolve, reject) => {
        const xhr = new XMLHttpRequest();
        xhr.open(method, url, true);
        
        xhr.onload = function() {
            if (xhr.status >= 200 && xhr.status < 300) {
                try {
                    const response = xhr.responseText ? JSON.parse(xhr.responseText) : null;
                    resolve(response);
                } catch (parseError) {
                    reject(new Error('Failed to parse response'));
                }
            } else {
                reject(new Error(`Request failed with status ${xhr.status}`));
            }
        };
        
        xhr.onerror = function() {
            reject(new Error('Network error occurred'));
        };
        
        xhr.send(data ? JSON.stringify(data) : null);
    });
}

Для Fetch API:

javascript
async function safeFetch(url, options = {}) {
    try {
        const response = await fetch(url, {
            ...options,
            headers: {
                'Content-Type': 'application/json',
                ...options.headers
            }
        });
        
        if (!response.ok) {
            const errorData = await response.json().catch(() => ({}));
            throw new Error(errorData.message || `HTTP ${response.status}`);
        }
        
        const contentType = response.headers.get('content-type');
        if (contentType && contentType.includes('application/json')) {
            return await response.json();
        }
        return await response.text();
    } catch (error) {
        console.error('Fetch error:', error);
        throw error;
    }
}

2. Создавайте переиспользуемые утилиты AJAX

Как предлагает Ibrahim Diallo, можно обернуть код в объект Ajax и использовать его во всём проекте.

javascript
const Ajax = {
    xhr: null,
    
    request: function(url, method, data, success, failure) {
        if (!this.xhr) {
            this.xhr = new XMLHttpRequest();
        }
        
        this.xhr.open(method, url, true);
        this.xhr.setRequestHeader('Content-Type', 'application/json');
        
        this.xhr.onload = function() {
            if (this.status >= 200 && this.status < 300) {
                try {
                    const response = this.responseText ? JSON.parse(this.responseText) : null;
                    success(response);
                } catch (e) {
                    failure('Invalid JSON response');
                }
            } else {
                failure(`Request failed with status ${this.status}`);
            }
        };
        
        this.xhr.onerror = function() {
            failure('Network error occurred');
        };
        
        this.xhr.send(data ? JSON.stringify(data) : null);
    },
    
    get: function(url, success, failure) {
        this.request(url, 'GET', null, success, failure);
    },
    
    post: function(url, data, success, failure) {
        this.request(url, 'POST', data, success, failure);
    }
};

3. Используйте перехватчики запросов/ответов

javascript
class HTTPClient {
    constructor(baseURL = '') {
        this.baseURL = baseURL;
    }
    
    async request(endpoint, options = {}) {
        const url = `${this.baseURL}${endpoint}`;
        const defaultOptions = {
            headers: {
                'Content-Type': 'application/json',
            },
        };
        
        const config = { ...defaultOptions, ...options };
        
        // Request interceptor
        if (this.requestInterceptor) {
            const interceptedConfig = await this.requestInterceptor(config);
            config.headers = { ...config.headers, ...interceptedConfig.headers };
        }
        
        try {
            const response = await fetch(url, config);
            
            // Response interceptor
            let data;
            if (response.ok) {
                data = await this.parseResponse(response);
            } else {
                data = await this.parseResponse(response);
                throw new Error(data.message || `HTTP ${response.status}`);
            }
            
            if (this.responseInterceptor) {
                return await this.responseInterceptor(data);
            }
            
            return data;
        } catch (error) {
            if (this.errorInterceptor) {
                return await this.errorInterceptor(error);
            }
            throw error;
        }
    }
    
    parseResponse(response) {
        const contentType = response.headers.get('content-type');
        if (contentType && contentType.includes('application/json')) {
            return response.json();
        }
        return response.text();
    }
    
    get(endpoint, options = {}) {
        return this.request(endpoint, { ...options, method: 'GET' });
    }
    
    post(endpoint, data, options = {}) {
        return this.request(endpoint, { 
            ...options, 
            method: 'POST', 
            body: JSON.stringify(data) 
        });
    }
    
    // Set interceptors
    setRequestInterceptor(interceptor) {
        this.requestInterceptor = interceptor;
    }
    
    setResponseInterceptor(interceptor) {
        this.responseInterceptor = interceptor;
    }
    
    setErrorInterceptor(interceptor) {
        this.errorInterceptor = interceptor;
    }
}

Стратегии обработки ошибок

Полноценная обработка ошибок для XMLHttpRequest

javascript
function robustXHR(url, options = {}) {
    return new Promise((resolve, reject) => {
        const xhr = new XMLHttpRequest();
        const {
            method = 'GET',
            data = null,
            timeout = 10000,
            headers = {}
        } = options;
        
        xhr.open(method, url, true);
        
        // Установка заголовков
        Object.entries(headers).forEach(([key, value]) => {
            xhr.setRequestHeader(key, value);
        });
        
        // Таймаут
        xhr.timeout = timeout;
        xhr.ontimeout = function() {
            reject(new Error('Request timeout'));
        };
        
        // Отслеживание прогресса
        if (options.onProgress) {
            xhr.onprogress = options.onProgress;
        }
        
        // Handlers
        xhr.onload = function() {
            try {
                if (xhr.status >= 200 && xhr.status < 300) {
                    const contentType = xhr.getResponseHeader('content-type');
                    let response;
                    
                    if (contentType && contentType.includes('application/json')) {
                        response = JSON.parse(xhr.responseText);
                    } else {
                        response = xhr.responseText;
                    }
                    
                    resolve({
                        data: response,
                        status: xhr.status,
                        headers: parseHeaders(xhr.getAllResponseHeaders())
                    });
                } else {
                    let errorData;
                    try {
                        errorData = JSON.parse(xhr.responseText);
                    } catch (e) {
                        errorData = { message: xhr.statusText };
                    }
                    
                    reject({
                        error: errorData,
                        status: xhr.status,
                        headers: parseHeaders(xhr.getAllResponseHeaders())
                    });
                }
            } catch (parseError) {
                reject({
                    error: { message: 'Failed to parse response' },
                    status: xhr.status,
                    originalError: parseError
                });
            }
        };
        
        xhr.onerror = function() {
            reject({
                error: { message: 'Network error occurred' },
                status: 0
            });
        };
        
        xhr.onabort = function() {
            reject({
                error: { message: 'Request was aborted' },
                status: 0
            });
        };
        
        xhr.send(data);
    });
}

function parseHeaders(headerString) {
    const headers = {};
    if (!headerString) return headers;
    
    const headerPairs = headerString.split('\u000d\u000a');
    for (let i = 0; i < headerPairs.length; i++) {
        const headerPair = headerPairs[i];
        const index = headerPair.indexOf('\u003a\u0020');
        if (index > 0) {
            const key = headerPair.substring(0, index);
            const value = headerPair.substring(index + 2);
            headers[key] = value;
        }
    }
    return headers;
}

Продвинутая обработка ошибок для Fetch API

javascript
class FetchError extends Error {
    constructor(message, status, response) {
        super(message);
        this.name = 'FetchError';
        this.status = status;
        this.response = response;
    }
}

async function enhancedFetch(url, options = {}) {
    const {
        method = 'GET',
        headers = {},
        body,
        timeout = 10000,
        retries = 3,
        retryDelay = 1000
    } = options;
    
    const controller = new AbortController();
    const timeoutId = setTimeout(() => controller.abort(), timeout);
    
    try {
        for (let attempt = 0; attempt < retries; attempt++) {
            try {
                const response = await fetch(url, {
                    method,
                    headers,
                    body,
                    signal: controller.signal
                });
                
                clearTimeout(timeoutId);
                
                if (!response.ok) {
                    let errorData;
                    try {
                        errorData = await response.json();
                    } catch (e) {
                        errorData = await response.text();
                    }
                    
                    throw new FetchError(
                        errorData.message || `HTTP ${response.status}`,
                        response.status,
                        errorData
                    );
                }
                
                const contentType = response.headers.get('content-type');
                let data;
                
                if (contentType && contentType.includes('application/json')) {
                    data = await response.json();
                } else {
                    data = await response.text();
                }
                
                return {
                    data,
                    status: response.status,
                    headers: Object.fromEntries(response.headers.entries())
                };
                
            } catch (error) {
                if (attempt === retries - 1) {
                    throw error;
                }
                
                await new Promise(resolve => setTimeout(resolve, retryDelay * (attempt + 1)));
            }
        }
    } finally {
        clearTimeout(timeoutId);
    }
}

Продвинутые техники и шаблоны

1. AJAX с кэшированием

javascript
class CachedHTTPClient {
    constructor(cacheSize = 100, ttl = 5 * 60 * 1000) { // 5 минут по умолчанию
        this.cache = new Map();
        this.cacheSize = cacheSize;
        this.ttl = ttl;
    }
    
    async request(url, options = {}) {
        const cacheKey = this.generateCacheKey(url, options);
        const cached = this.cache.get(cacheKey);
        
        if (cached && Date.now() - cached.timestamp < this.ttl) {
            return cached.data;
        }
        
        const data = await this.fetchWithCache(url, options);
        this.cache.set(cacheKey, {
            data,
            timestamp: Date.now()
        });
        
        // Сохраняем размер кэша
        if (this.cache.size > this.cacheSize) {
            const oldestKey = this.cache.keys().next().value;
            this.cache.delete(oldestKey);
        }
        
        return data;
    }
    
    generateCacheKey(url, options) {
        const sortedOptions = Object.keys(options)
            .sort()
            .reduce((obj, key) => {
                obj[key] = options[key];
                return obj;
            }, {});
        
        return `${url}_${JSON.stringify(sortedOptions)}`;
    }
    
    async fetchWithCache(url, options) {
        // Реализация с использованием предпочитаемого метода fetch
        return enhancedFetch(url, options);
    }
}

2. Бatching запросов

javascript
class BatchRequestManager {
    constructor(batchSize = 5, batchDelay = 100) {
        this.batchSize = batchSize;
        this.batchDelay = batchDelay;
        this.pendingRequests = [];
        this.batchTimer = null;
    }
    
    async addRequest(requestConfig) {
        return new Promise((resolve, reject) => {
            this.pendingRequests.push({
                config: requestConfig,
                resolve,
                reject
            });
            
            if (this.pendingRequests.length >= this.batchSize) {
                this.processBatch();
            } else if (!this.batchTimer) {
                this.batchTimer = setTimeout(() => {
                    this.processBatch();
                }, this.batchDelay);
            }
        });
    }
    
    async processBatch() {
        if (this.batchTimer) {
            clearTimeout(this.batchTimer);
            this.batchTimer = null;
        }
        
        const batch = this.pendingRequests.splice(0, this.batchSize);
        
        try {
            // Можно оптимизировать для параллельных запросов
            const results = await Promise.all(
                batch.map(async (item) => {
                    try {
                        const result = await enhancedFetch(item.config.url, item.config.options);
                        item.resolve(result);
                    } catch (error) {
                        item.reject(error);
                    }
                })
            );
        } catch (error) {
            batch.forEach(item => item.reject(error));
        }
        
        // Обрабатываем оставшиеся запросы, если есть
        if (this.pendingRequests.length > 0) {
            this.batchTimer = setTimeout(() => {
                this.processBatch();
            }, this.batchDelay);
        }
    }
}

3. AJAX с TypeScript‑подобным интерфейсом

javascript
interface RequestOptions {
    method?: 'GET' | 'POST' | 'PUT' | 'DELETE' | 'PATCH';
    headers?: Record<string, string>;
    body?: any;
    timeout?: number;
    retries?: number;
    retryDelay?: number;
}

interface Response<T = any> {
    data: T;
    status: number;
    headers: Record<string, string>;
}

interface APIError {
    message: string;
    status?: number;
    response?: any;
    originalError?: Error;
}

class APIClient {
    private baseURL: string;
    private defaultHeaders: Record<string, string>;
    
    constructor(baseURL: string = '', defaultHeaders: Record<string, string> = {}) {
        this.baseURL = baseURL;
        this.defaultHeaders = {
            'Content-Type': 'application/json',
            ...defaultHeaders
        };
    }
    
    async request<T = any>(endpoint: string, options: RequestOptions = {}): Promise<Response<T>> {
        const url = `${this.baseURL}${endpoint}`;
        const {
            method = 'GET',
            headers = {},
            body,
            timeout = 10000,
            retries = 3,
            retryDelay = 1000
        } = options;
        
        const mergedHeaders = { ...this.defaultHeaders, ...headers };
        
        const controller = new AbortController();
        const timeoutId = setTimeout(() => controller.abort(), timeout);
        
        try {
            for (let attempt = 0; attempt < retries; attempt++) {
                try {
                    const response = await fetch(url, {
                        method,
                        headers: mergedHeaders,
                        body: body ? JSON.stringify(body) : undefined,
                        signal: controller.signal
                    });
                    
                    clearTimeout(timeoutId);
                    
                    if (!response.ok) {
                        let errorData;
                        try {
                            errorData = await response.json();
                        } catch (e) {
                            errorData = await response.text();
                        }
                        
                        throw {
                            message: errorData.message || `HTTP ${response.status}`,
                            status: response.status,
                            response: errorData
                        };
                    }
                    
                    const contentType = response.headers.get('content-type');
                    let data: T;
                    
                    if (contentType && contentType.includes('application/json')) {
                        data = await response.json();
                    } else {
                        data = await response.text() as T;
                    }
                    
                    return {
                        data,
                        status: response.status,
                        headers: Object.fromEntries(response.headers.entries())
                    };
                    
                } catch (error) {
                    if (attempt === retries - 1) {
                        throw error;
                    }
                    
                    await new Promise(resolve => setTimeout(resolve, retryDelay * (attempt + 1)));
                }
            }
        } finally {
            clearTimeout(timeoutId);
        }
    }
    
    async get<T = any>(endpoint: string, options: Omit<RequestOptions, 'method'> = {}): Promise<Response<T>> {
        return this.request<T>(endpoint, { ...options, method: 'GET' });
    }
    
    async post<T = any>(endpoint: string, data: any, options: Omit<RequestOptions, 'method' | 'body'> = {}): Promise<Response<T>> {
        return this.request<T>(endpoint, { ...options, method: 'POST', body: data });
    }
    
    async put<T = any>(endpoint: string, data: any, options: Omit<RequestOptions, 'method' | 'body'> = {}): Promise<Response<T>> {
        return this.request<T>(endpoint, { ...options, method: 'PUT', body: data });
    }
    
    async delete<T = any>(endpoint: string, options: Omit<RequestOptions, 'method'> = {}): Promise<Response<T>> {
        return this.request<T>(endpoint, { ...options, method: 'DELETE' });
    }
}

Совместимость с браузерами

Совместимость XMLHttpRequest

XMLHttpRequest имеет отличную поддержку во всех современных браузерах и даже в старых, таких как Internet Explorer 8 и выше. Как отмечает Mozilla Developer Network, «Запрос, выполненный через XMLHttpRequest, может получать данные как асинхронно, так и синхронно».

Совместимость Fetch API

Fetch API поддерживается:

  • Во всех современных браузерах (Chrome, Firefox, Safari, Edge)
  • В Internet Explorer нет нативной поддержки Fetch
  • В Node.js 18+ поддерживается Fetch

Для старых браузеров может потребоваться polyfill:

html
<!-- Для браузеров, которые не поддерживают Fetch API -->
<script src="https://unpkg.com/whatwg-fetch@3.6.2/dist/fetch.umd.js"></script>

Обнаружение возможностей

javascript
function isFetchSupported() {
    return 'fetch' in window && typeof window.fetch === 'function';
}

function isXHRSupported() {
    return 'XMLHttpRequest' in window && typeof window.XMLHttpRequest === 'function';
}

function getBestAvailableMethod() {
    if (isFetchSupported()) {
        return 'fetch';
    } else if (isXHRSupported()) {
        return 'xhr';
    } else {
        throw new Error('No AJAX method available in this browser');
    }
}

// Использование
const ajaxMethod = getBestAvailableMethod();
console.log('Using AJAX method:', ajaxMethod);

Прогрессивное улучшение

javascript
class ProgressiveAjax {
    constructor() {
        this.method = this.determineBestMethod();
    }
    
    determineBestMethod() {
        try {
            if (window.fetch) {
                return 'fetch';
            } else if (window.XMLHttpRequest) {
                return 'xhr';
            } else {
                throw new Error('No AJAX support available');
            }
        } catch (e) {
            console.error('AJAX not supported:', e);
            return null;
        }
    }
    
    async request(url, options = {}) {
        if (!this.method) {
            throw new Error('AJAX not supported in this browser');
        }
        
        if (this.method === 'fetch') {
            return this.fetchRequest(url, options);
        } else {
            return this.xhrRequest(url, options);
        }
    }
    
    async fetchRequest(url, options) {
        const fetchOptions = {
            method: options.method || 'GET',
            headers: options.headers || {},
            body: options.body ? JSON.stringify(options.body) : undefined
        };
        
        const response = await fetch(url, fetchOptions);
        
        if (!response.ok) {
            throw new Error(`HTTP ${response.status}`);
        }
        
        return response.json();
    }
    
    xhrRequest(url, options) {
        return new Promise((resolve, reject) => {
            const xhr = new XMLHttpRequest();
            xhr.open(options.method || 'GET', url, true);
            
            // Установка заголовков
            Object.entries(options.headers || {}).forEach(([key, value]) => {
                xhr.setRequestHeader(key, value);
            });
            
            xhr.onload = function() {
                if (xhr.status >= 200 && xhr.status < 300) {
                    try {
                        resolve(JSON.parse(xhr.responseText));
                    } catch (e) {
                        reject(new Error('Invalid JSON response'));
                    }
                } else {
                    reject(new Error(`HTTP ${xhr.status}`));
                }
            };
            
            xhr.onerror = function() {
                reject(new Error('Network error'));
            };
            
            xhr.send(options.body ? JSON.stringify(options.body) : null);
        });
    }
}

Советы по оптимизации производительности

1. Debounce запросов

javascript
class DebouncedRequest {
    constructor(delay = 300) {
        this.delay = delay;
        this.timeout = null;
        this.pendingRequest = null;
    }
    
    async request(requestFunction, ...args) {
        return new Promise((resolve, reject) => {
            if (this.timeout) {
                clearTimeout(this.timeout);
            }
            
            this.pendingRequest = { resolve, reject };
            
            this.timeout = setTimeout(async () => {
                try {
                    const result = await requestFunction(...args);
                    this.pendingRequest.resolve(result);
                } catch (error) {
                    this.pendingRequest.reject(error);
                } finally {
                    this.timeout = null;
                    this.pendingRequest = null;
                }
            }, this.delay);
        });
    }
}

// Использование
const debouncedFetch = new DebouncedRequest(500);

// Множественные быстрые вызовы приведут к одному фактическому запросу
debouncedFetch.fetch('/api/data');
debouncedFetch.fetch('/api/data');
debouncedFetch.fetch('/api/data');

2. Стратегия кэширования запросов

javascript
class RequestCache {
    constructor(maxSize = 100, ttl = 5 * 60 * 1000) {
        this.cache = new Map();
        this.maxSize = maxSize;
        this.ttl = ttl;
    }
    
    get(key) {
        const item = this.cache.get(key);
        if (item && Date.now() - item.timestamp < this.ttl) {
            return item.data;
        }
        return null;
    }
    
    set(key, data) {
        if (this.cache.size >= this.maxSize) {
            // Удаляем старейший элемент
            const oldestKey = this.cache.keys().next().value;
            this.cache.delete(oldestKey);
        }
        
        this.cache.set(key, {
            data,
            timestamp: Date.now()
        });
    }
    
    clear() {
        this.cache.clear();
    }
    
    generateKey(url, options) {
        const sortedOptions = Object.keys(options || {})
            .sort()
            .reduce((obj, key) => {
                obj[key] = options[key];
                return obj;
            }, {});
        
        return `${url}_${JSON.stringify(sortedOptions)}`;
    }
}

3. Обработка запросов с учётом состояния соединения

javascript
class ConnectionAwareHTTP {
    constructor() {
        this.connection = navigator.connection || navigator.mozConnection || navigator.webkitConnection;
        this.updateConnectionInfo();
        
        // Слушаем изменения соединения
        if (this.connection) {
            this.connection.addEventListener('change', () => {
                this.updateConnectionInfo();
            });
        }
    }
    
    updateConnectionInfo() {
        this.effectiveType = this.connection ? this.connection.effectiveType : '4g';
        this.downlink = this.connection ? this.connection.downlink : 10;
        this.rtt = this.connection ? this.connection.rtt : 100;
    }
    
    async request(url, options = {}) {
        const adjustedOptions = this.adjustOptionsForConnection(options);
        return enhancedFetch(url, adjustedOptions);
    }
    
    adjustOptionsForConnection(options) {
        const adjustedOptions = { ...options };
        
        if (this.effectiveType === 'slow-2g' || this.effectiveType === '2g') {
            // Для медленных соединений уменьшаем таймауты и включаем retries
            adjustedOptions.timeout = 30000; // 30 секунд
            adjustedOptions.retries = 5;
            adjustedOptions.retryDelay = 2000;
            
            // Можно уменьшить размер данных
            if (options.body && typeof options.body === 'object') {
                // Здесь можно реализовать сжатие данных
            }
        } else if (this.effectiveType === '4g') {
            // Для быстрых соединений используем агрессивные таймауты
            adjustedOptions.timeout = 5000;
            adjustedOptions.retries = 2;
            adjustedOptions.retryDelay = 500;
        }
        
        return adjustedOptions;
    }
}

4. Приоритезация запросов

javascript
class RequestPriorityManager {
    constructor() {
        this.highPriorityQueue = [];
        this.normalPriorityQueue = [];
        this.lowPriorityQueue = [];
        this.isProcessing = false;
    }
    
    addRequest(request, priority = 'normal') {
        const requestWrapper = {
            request,
            priority,
            timestamp: Date.now()
        };
        
        switch (priority) {
            case 'high':
                this.highPriorityQueue.push(requestWrapper);
                break;
            case 'normal':
                this.normalPriorityQueue.push(requestWrapper);
                break;
            case 'low':
                this.lowPriorityQueue.push(requestWrapper);
                break;
        }
        
        if (!this.isProcessing) {
            this.processQueue();
        }
    }
    
    async processQueue() {
        this.isProcessing = true;
        
        try {
            // Обрабатываем запросы высокого приоритета
            while (this.highPriorityQueue.length > 0) {
                const requestWrapper = this.highPriorityQueue.shift();
                await this.executeRequest(requestWrapper);
            }
            
            // Затем обычные
            while (this.normalPriorityQueue.length > 0) {
                const requestWrapper = this.normalPriorityQueue.shift();
                await this.executeRequest(requestWrapper);
            }
            
            // И наконец низкие
            while (this.lowPriorityQueue.length > 0) {
                const requestWrapper = this.lowPriorityQueue.shift();
                await this.executeRequest(requestWrapper);
            }
        } finally {
            this.isProcessing = false;
        }
    }
    
    async executeRequest(requestWrapper) {
        try {
            const result = await requestWrapper.request();
            return result;
        } catch (error) {
            console.error('Request failed:', error);
            throw error;
        }
    }
}

Источники

  1. How can I make an AJAX call without jQuery? - Stack Overflow
  2. How to Make an AJAX Call Without jQuery Using Vanilla JavaScript — codestudy.net
  3. Ajax Requests – You Don’t Need jQuery! – Garstasio.com
  4. A Guide to Vanilla Ajax Without jQuery — SitePoint
  5. How to Make an AJAX Call without jQuery - StackAbuse
  6. Ajax without jQuery - JavaScript - Ibrahim Diallo
  7. Making AJAX requests with native JavaScript | Go Make Things
  8. CoffeeScript Cookbook » Ajax Request Without jQuery
  9. How to Make AJAX Calls Without jQuery? - DevBabu
  10. How can I make an AJAX call without jQuery? - Matheus Mello
  11. Fetch API vs XMLHttpRequest - Stack Overflow
  12. XMLHttpRequest API - Web APIs | MDN
  13. The Fetch API performance vs. XHR in vanilla JS | Go Make Things
  14. Fetch API Vs XMLHttpRequest - Tutorialspoint
  15. Using XMLHttpRequest - Web APIs | MDN

Заключение

Ключевые выводы

  1. Выберите подходящий инструмент: XMLHttpRequest обеспечивает более широкую совместимость и более детальный контроль, в то время как Fetch предлагает более чистый синтаксис с промисами для современных приложений.
  2. Всегда реализуйте обработку ошибок: Оба подхода требуют всесторонней обработки сетевых ошибок, ошибок сервера и проблем с разбором данных.
  3. Создавайте переиспользуемые утилиты: Оберните AJAX‑функциональность в собственные классы или утилиты, чтобы поддерживать единообразие и уменьшить дублирование кода.
  4. Учитывайте совместимость браузеров: Используйте обнаружение возможностей и прогрессивное улучшение, чтобы гарантировать работу в разных браузерах и устройствах.
  5. Оптимизируйте производительность: Реализуйте кэширование, дебаунсинг, батчинг и приоритезацию запросов для улучшения пользовательского опыта и снижения нагрузки на сервер.

Практические рекомендации

  • Для новых проектов: Начните с Fetch API, если не требуется поддержка Internet Explorer или специфический контроль над запросами.
  • Для наследуемых приложений: Рассмотрите использование XMLHttpRequest или polyfill для более широкой совместимости.
  • Для сложных приложений: Постройте надёжный HTTP‑клиент с интерсепторами, кэшированием и продвинутой обработкой ошибок.
  • Для мобильных пользователей: Реализуйте обработку запросов с учётом состояния соединения, чтобы оптимизировать работу в условиях слабой сети.

Часто задаваемые вопросы

  • Что лучше: XMLHttpRequest или Fetch API? В большинстве случаев предпочтительнее Fetch API из‑за более чистого синтаксиса и промис‑обработки, но XMLHttpRequest остаётся ценным для совместимости с устаревшими браузерами и специфических случаев.
  • Как обрабатывать кросс‑доменные запросы? Оба подхода автоматически обрабатывают CORS, но сервер должен включать соответствующие заголовки CORS для успешных запросов.
  • Можно ли использовать AJAX в серверном JavaScript? Да, как XMLHttpRequest, так и Fetch API работают в Node.js 18+ и других средах серверного JavaScript, что делает их пригодными для изоморфных приложений.
Авторы
Проверено модерацией
Модерация