Форум программистов, компьютерный форум, киберфорум
Reangularity
Войти
Регистрация
Восстановить пароль

Асинхронный JavaScript: Промисы, Async/Await и Fetch API

Запись от Reangularity размещена 27.04.2025 в 18:55
Показов 3320 Комментарии 0

Нажмите на изображение для увеличения
Название: 8e643b25-1380-4bc7-a5bc-516d19195419.jpg
Просмотров: 73
Размер:	232.0 Кб
ID:	10686
Пользователь заходит на веб-страницу, нажимает кнопку и... ничего не происходит. Сайт словно замер. Через несколько секунд всё внезапно оживает, но пользователь уже успел закрыть вкладку. Знакомая картина? Именно с такими проблемами сталкиваются разработчики, использующие синхронный подход в JavaScript.

В веб-разработки синхронный код — настоящая головная боль. Когда операции выполняются последовательно, одна за другой, любая трудоёмкая задача блокирует всё остальное. Представьте очередь в супермаркете, где один покупатель с огромной корзиной задерживает всех остальных. Ни о какой эффективности речи не идёт. Синхронный код выполняется в одном потоке, что создаёт серьезные проблемы при работе с операциями ввода-вывода. Запросы к серверу, чтение файлов, ожидание ввода пользователя — все эти действия могут занимать значительное время, и в синхронной модели интерфейс приложения будет "замораживаться" на протяжении всей операции.

JavaScript
1
2
3
4
5
6
7
8
9
10
11
12
13
14
// Пример синхронного кода, блокирующего интерфейс
function getDataSync() {
    // Эта функция занимает 5 секунд
    const startTime = new Date().getTime();
    while (new Date().getTime() < startTime + 5000) {
        // Блокируем поток выполнения на 5 секунд
    }
    return "Данные получены!";
}
 
console.log("Начинаем запрос...");
const result = getDataSync(); // Здесь весь интерфейс замрёт на 5 секунд
console.log(result);
console.log("Запрос завершен.");
Поскольку JavaScript изначально был создан для взаимодействия с пользователем в браузере, проблема блокировки интерфейса быстро стала очевидной. Разработчики начали искать способы выполнять длительные операции без блокировки основного потока. Первым широко используемым решением стали колбэки (callback functions) — функции, которые передаются в качестве аргументов другим функциям и вызываются при завершении асинхронной операции:

JavaScript
1
2
3
4
5
6
7
8
9
10
11
12
// Пример асинхронного кода с использованием колбэков
function getDataAsync(callback) {
    setTimeout(() => {
        callback("Данные получены асинхронно!");
    }, 5000);
}
 
console.log("Начинаем асинхронный запрос...");
getDataAsync(function(result) {
    console.log(result);
});
console.log("Запрос отправлен, продолжаем работу.");
В этом примере функция getDataAsync не блокирует выполнение основного потока. После её вызова сразу выполняется следующая строка кода, а результат обрабатывается только после получения данных. Колбэки решили проблему блокировки, но породили новую — "callback hell" или "ад колбэков". При наличии множества вложенных асинхронных операций код превращается в пирамиду из фигурных скобок, которую трудно читать и поддерживать:

JavaScript
1
2
3
4
5
6
7
8
9
10
11
getData(function(a) {
    getMoreData(a, function(b) {
        getEvenMoreData(b, function(c) {
            getYetEvenMoreData(c, function(d) {
                getFinalData(d, function(result) {
                    console.log("Получен финальный результат:", result);
                });
            });
        });
    });
});
Эта проблема стала настолько распространенной, что вынудила сообщество JavaScript искать более элегантные решения. Так началась эволюция асинхронных подходов, которая привела к появлению промисов (Promises) в ES6, а затем и синтаксиса async/await в ES2017.

Ключевым моментом в понимании асинхронного программирования в JavaScript является концепция Event Loop (цикл событий) — механизм, который позволяет JavaScript выполнять неблокирующие операции, несмотря на то, что язык является однопоточным. Event Loop постоянно проверяет стек вызовов и очереди задач, выполняя код в определённом порядке.

Концепция асинхронного программирования не уникальна для JavaScript. Другие языки программирования имеют свои механизмы работы с асинхронными операциями:
  • Python использует генераторы, корутины и библиотеку asyncio.
  • C# предлагает ключевые слова async и await (которые вдохновили подобный синтаксис в JavaScript).
  • Java использует Future, CompletableFuture и реактивные расширения.
  • Rust предоставляет систему Future и crate tokio для асинхронного ввода-вывода.

Каждый из этих подходов имеет свои особенности, но цель одна — эффективная обработка операций, которые могут занимать много времени, без блокировки основного потока выполнения программы.
По мере роста сложности веб-приложений становится критически важным понимать, как именно работает Event Loop в JavaScript. Это не просто абстрактное понятие — это конкретный механизм, определяющий порядок выполнения асинхронного кода.

Анатомия Event Loop: как JavaScript справляется с асинхронностью



Event Loop состоит из нескольких ключевых компонентов:
1. Call Stack (Стек вызовов) — структура данных, отслеживающая текущие функции в процессе выполнения.
2. Callback Queue (Очередь колбэков) — хранит функции, готовые к выполнению после завершения асинхронных операций.
3. Web APIs — браузерные API, выполняющие асинхронные операции (setTimeout, fetch, DOM events).
Процесс работы Event Loop можно описать следующим алгоритмом:

JavaScript
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
// Псевдокод работы Event Loop
while (true) {
    // 1. Выполнить весь код из стека вызовов
    while (callStack.isNotEmpty()) {
        executeCurrentFunction();
    }
    
    // 2. Проверить очередь микрозадач и выполнить их
    while (microtaskQueue.isNotEmpty()) {
        executeNextMicrotask();
    }
    
    // 3. Проверить очередь макрозадач и выполнить одну
    if (taskQueue.isNotEmpty()) {
        executeNextTask();
    }
    
    // 4. Отрисовка пользовательского интерфейса
    renderUIIfNeeded();
}
При этом существует важное разделение задач на микрозадачи (microtasks) и макрозадачи (tasks). К микрозадачам относятся обработчики промисов (.then(), .catch(), .finally()), в то время как к макрозадачам — таймеры, I/O операции и обработчики событий DOM.

Асинхронно вывести данные в формате json через fetch async
Здравствуйте. Не понимаю как вывести асинхронно данные в формате json. var city = { ...

Знакомство с промисами и async await
Начал изучать промисы и встретилась статья, где пишут на синтаксисе es6 используя then, и синтаксис...

Промис с колбеком async/await
Срочно надо написать простую функцию. Есть функция 1 и функция 2. Вторая должна вызываться...

Элементарный промис async/await
Всем привет, Осваиваю тему промисов. Написал свой первый элементарный промис, но не работает....


Микро- и макрозадачи: тонкости очерёдности



Отличие между микро- и макрозадачами стало особенно важным с появлением промисов. Рассмотрим пример:

JavaScript
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
console.log('Скрипт начался');
 
setTimeout(() => {
  console.log('Таймер 1');
}, 0);
 
Promise.resolve()
  .then(() => console.log('Промис 1'))
  .then(() => console.log('Промис 2'));
 
setTimeout(() => {
  console.log('Таймер 2');
}, 0);
 
console.log('Скрипт завершился');
Результат выполнения:
JavaScript
1
2
3
4
5
6
Скрипт начался
Скрипт завершился
Промис 1
Промис 2
Таймер 1
Таймер 2
Причина такого порядка в том, что все микрозадачи выполняются до того, как Event Loop перейдёт к следующей макрозадаче. Это гарантирует, что состояние приложения остаётся консистентным в рамках одного "тика" Event Loop.

Инструменты для отладки асинхронного кода



Отладка асинхронного кода может превратиться в настоящий квест. К счастью, современные браузеры предоставляют мощные инструменты для анализа асинхронных операций:

1. Async/Await стек вызовов в Chrome DevTools



В последних версиях Chrome DevTools появилась возможность отслеживать полный стек вызовов для асинхронных функций. При установке точки останова в функции, вызываемой асинхронно, вы увидите полную цепочку вызовов, включая асинхронные переходы.

2. Async stack traces



Chrome DevTools также поддерживает функцию "Async stack traces", которая позволяет видеть полную историю асинхронных вызовов, приведших к текущей точке выполнения. Для включения этой функции:
1. Откройте DevTools (F12 или Cmd+Option+I на Mac).
2. Перейдите во вкладку Sources.
3. В правой панели откройте вкладку Call Stack.
4. Активируйте опцию "Async".

3. Performance Timeline



Вкладка Performance позволяет записывать и анализировать выполнение JavaScript-кода, включая асинхронные операции:

JavaScript
1
2
3
4
5
6
7
8
9
// Пример использования Performance API в коде
performance.mark('start');
 
fetchData()
  .then(processData)
  .then(() => {
    performance.mark('end');
    performance.measure('fetch and process', 'start', 'end');
  });
Для анализа производительности асинхронного кода особенно полезны следующие функции:
Flame Chart: показывает активность JavaScript во времени, включая колбэки и асинхронные операции.
Main: детальная хронология всех событий на главном потоке, включая выполнение скриптов, сборку мусора, и отрисовку.
Network: временная шкала всех сетевых запросов, позволяющая идентифицировать узкие места.

4. Console.time и console.timeEnd



Эти методы позволяют измерять время выполнения асинхронных операций:

JavaScript
1
2
3
4
5
6
7
console.time('asyncOperation');
 
fetchData()
  .then(result => {
    console.timeEnd('asyncOperation');
    console.log(result);
  });

Event Loop в разных средах JavaScript



Стоит отметить, что реализация Event Loop в Node.js отличается от браузерной. Node.js использует библиотеку libuv для управления асинхронными операциями и разделяет очередь задач на несколько фаз:
1. timers: колбэки от setTimeout() и setInterval().
2. pending callbacks: отложенные колбэки системных операций.
3. idle, prepare: внутренние фазы.
4. poll: получение новых событий ввода/вывода.
5. check: колбэки от setImmediate().
6. close callbacks: обработчики закрытия, например socket.on('close', ...).

Понимание этих различий критически важно при разработке изоморфного JavaScript, который может выполняться как в браузере, так и на сервере.

Асинхронное программирование в JavaScript прошло долгий путь от колбэков до современных промисов и async/await. Понимание внутренних механизмов, таких как Event Loop, микро- и макрозадачи, становится критически важным для создания эффективных и отзывчивых веб-приложений. В следующих разделах мы подробно рассмотрим промисы, async/await и Fetch API — три кита современного асинхронного JavaScript.

Промисы - основа современного асинхронного кода



В мире асинхронного JavaScript промисы (Promises) произвели настоящую революцию. Появившись в стандарте ES6 (ECMAScript 2015), они предложили элегантное решение проблемы "ада колбэков" и навсегда изменили подход к написанию асинхронного кода.

Промис — это объект, представляющий результат асинхронной операции, который может находиться в одном из трёх состояний:
1. Pending (ожидание) — начальное состояние, операция ещё выполняется.
2. Fulfilled (выполнено) — операция успешно завершена.
3. Rejected (отклонено) — операция завершилась с ошибкой.
Ключевая особенность промисов — они позволяют отделить логику обработки результата от самой асинхронной операции.

Принципы работы промисов



Для создания промиса используется конструктор Promise, который принимает функцию-исполнитель (executor) с двумя аргументами: resolve и reject.

JavaScript
1
2
3
4
5
6
7
8
9
10
const myPromise = new Promise((resolve, reject) => {
  // Асинхронная операция
  const success = true;
  
  if (success) {
    resolve('Операция выполнена успешно!');
  } else {
    reject(new Error('Что-то пошло не так...'));
  }
});
Функция-исполнитель запускается синхронно при создании промиса. В ней происходит асинхронная операция, после завершения которой вызывается либо resolve (с результатом), либо reject (с ошибкой).
Вызов resolve переводит промис в состояние "fulfilled", а вызов reject — в состояние "rejected". После перехода в одно из этих состояний, промис становится "settled" (установленным) и его состояние уже не может измениться.

Потребление промисов



Для обработки результатов промиса используются методы .then(), .catch() и .finally():

JavaScript
1
2
3
4
5
6
7
8
9
10
11
12
13
14
myPromise
  .then(result => {
    console.log('Успех:', result);
    return 'Дополнительная обработка';
  })
  .then(newResult => {
    console.log('Еще обработка:', newResult);
  })
  .catch(error => {
    console.error('Ошибка:', error.message);
  })
  .finally(() => {
    console.log('Выполняется всегда, независимо от результата');
  });
Метод .then() принимает два необязательных аргумента: функцию для обработки успешного результата и функцию для обработки ошибки. Второй аргумент часто опускают в пользу отдельного метода .catch(), который делает код более читаемым.
Метод .finally() выполняется всегда, независимо от того, был промис выполнен успешно или отклонен. Этот метод идеально подходит для очистки ресурсов или скрытия индикаторов загрузки.

Цепочки промисов



Одно из главных преимуществ промисов — возможность создавать цепочки асинхронных операций, избегая глубокой вложенности:

JavaScript
1
2
3
4
5
6
7
8
9
10
11
12
13
fetchUserData(userId)
  .then(userData => {
    return fetchUserPosts(userData.id);
  })
  .then(posts => {
    return fetchPostComments(posts[0].id);
  })
  .then(comments => {
    console.log('Комментарии к первому посту:', comments);
  })
  .catch(error => {
    console.error('Произошла ошибка в цепочке:', error);
  });
Каждый вызов .then() возвращает новый промис, который резолвится значением, возвращаемым из колбэк-функции. Если колбэк возвращает промис, внешний промис выполнится только после разрешения вложенного.
Это позволяет писать последовательные асинхронные операции в плоском стиле, а не в виде вложенных колбэков.

Обработка ошибок



В промисах реализована элегантная система обработки ошибок. Если в цепочке промисов происходит ошибка, выполнение переходит к ближайшему обработчику .catch():

JavaScript
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
fetchData()
  .then(data => {
    // Если здесь будет ошибка или Promise.reject
    const processed = processData(data);
    return processed;
  })
  .then(processed => {
    // Этот блок будет пропущен при ошибке выше
    displayData(processed);
  })
  .catch(error => {
    // Сюда попадут все ошибки из предыдущих блоков
    console.error('Ошибка в цепочке:', error);
    showErrorMessage();
  });
Такой подход значительно упрощает обработку ошибок по сравнению с колбэками, где приходилось проверять ошибку в каждом колбэке.
Промисы автоматически перехватывают исключения, выброшенные в колбэках, преобразуя их в отклонённые промисы:

JavaScript
1
2
3
4
5
6
7
8
9
Promise.resolve('данные')
  .then(data => {
    throw new Error('Что-то пошло не так!');
    // Или return Promise.reject(new Error('...'));
  })
  .catch(error => {
    // Оба типа ошибок будут перехвачены здесь
    console.error(error.message);
  });

Статические методы Promise



Класс Promise имеет несколько полезных статических методов для работы с промисами:

Promise.resolve() и Promise.reject()



Эти методы создают промисы, которые сразу переходят в состояние fulfilled или rejected:

JavaScript
1
2
3
4
5
// Создаёт выполненный промис с результатом 42
const resolved = Promise.resolve(42);
 
// Создаёт отклонённый промис с указанной ошибкой
const rejected = Promise.reject(new Error('Отказано!'));
Эти методы особенно полезны, когда нужно вернуть промис из функции в зависимости от некоторого условия:

JavaScript
1
2
3
4
5
6
7
function checkValue(value) {
  if (typeof value === 'number') {
    return Promise.resolve(value * 2);
  } else {
    return Promise.reject(new Error('Ожидалось число'));
  }
}

Promise.all()



Метод Promise.all() принимает массив промисов и возвращает новый промис, который выполнится, когда будут выполнены все промисы из массива:

JavaScript
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
const promises = [
  fetch('/api/users'),
  fetch('/api/posts'),
  fetch('/api/comments')
];
 
Promise.all(promises)
  .then(responses => {
    // Массив результатов в том же порядке, что и входные промисы
    return Promise.all(responses.map(response => response.json()));
  })
  .then(data => {
    const [users, posts, comments] = data;
    // Используем все данные, полученные параллельно
  })
  .catch(error => {
    // Если хотя бы один промис отклонён, 
    // весь Promise.all будет отклонён
    console.error('Ошибка в одном из запросов:', error);
  });
Этот метод особенно полезен для параллельного выполнения независимых асинхронных операций и ожидания завершения всех из них.

Promise.race()



Метод Promise.race() также принимает массив промисов, но возвращает промис, который выполнится или будет отклонён сразу же, как только выполнится или будет отклонён первый из промисов входного массива:

JavaScript
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
const timeoutPromise = new Promise((_, reject) => {
  setTimeout(() => reject(new Error('Timeout!')), 5000);
});
 
const dataPromise = fetch('/api/data');
 
Promise.race([dataPromise, timeoutPromise])
  .then(result => {
    // Вызовется, если fetch завершится быстрее таймаута
    console.log('Данные получены вовремя:', result);
  })
  .catch(error => {
    // Вызовется, если произойдёт таймаут или ошибка в fetch
    console.error('Ошибка или таймаут:', error);
  });
Этот пример показывает, как можно реализовать таймаут для асинхронной операции, которая сама по себе не поддерживает таймауты.
В следующем разделе мы рассмотрим более современные методы Promise API, такие как Promise.allSettled() и Promise.any(), а также углубимся в работу промисов в контексте цикла событий JavaScript.

Promise.allSettled()



В отличие от Promise.all(), который отклоняется при первой же ошибке, метод Promise.allSettled() ожидает завершения всех промисов независимо от результата. Этот метод, появившийся в ES2020, особенно полезен, когда нужно выполнить несколько независимых операций и получить все результаты, даже если некоторые из них завершились с ошибкой:

JavaScript
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
const promises = [
  fetch('/api/users').then(r => r.json()),
  fetch('/api/nonexistent').then(r => r.json()),
  fetch('/api/posts').then(r => r.json())
];
 
Promise.allSettled(promises)
  .then(results => {
    // results - массив объектов с информацией о результате каждого промиса
    results.forEach((result, index) => {
      if (result.status === 'fulfilled') {
        console.log(`Промис ${index} выполнен с результатом:`, result.value);
      } else {
        console.log(`Промис ${index} отклонён с ошибкой:`, result.reason);
      }
    });
    
    // Можно обработать успешные результаты, игнорируя ошибки
    const successfulData = results
      .filter(result => result.status === 'fulfilled')
      .map(result => result.value);
    
    console.log('Успешно полученные данные:', successfulData);
  });
Каждый элемент в массиве результатов имеет структуру:
Для успешных промисов: `{ status: "fulfilled", value: результат }`
Для отклонённых промисов: `{ status: "rejected", reason: ошибка }`

Promise.any()



Метод Promise.any(), добавленный в ES2021, является противоположностью Promise.all(). Он возвращает первый успешно выполненный промис из списка, игнорируя ошибки:

JavaScript
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
const promises = [
  fetch('/api/slow-server').then(r => r.json()),
  fetch('/api/fast-server').then(r => r.json()),
  fetch('/api/backup-server').then(r => r.json())
];
 
Promise.any(promises)
  .then(result => {
    // Получим результат от самого быстрого сервера
    console.log('Первый успешный результат:', result);
  })
  .catch(errors => {
    // AggregateError - новый тип ошибки, содержащий массив всех ошибок
    console.error('Все промисы были отклонены:', errors);
  });
Если все промисы отклонены, Promise.any() выбрасывает новый тип ошибки — AggregateError, содержащий массив всех ошибок.

Трансформация колбэков в промисы



Многие старые API и библиотеки до сих пор используют колбэки. Процесс "промисификации" — это преобразование функций, использующих колбэки, в функции, возвращающие промисы:

JavaScript
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
// Функция с колбэками
function getDataWithCallback(id, callback) {
  setTimeout(() => {
    if (id > 0) {
      callback(null, { id, name: [INLINE]Item ${id}[/INLINE] });
    } else {
      callback(new Error('Invalid ID'));
    }
  }, 1000);
}
 
// Промисифицированная версия
function getDataWithPromise(id) {
  return new Promise((resolve, reject) => {
    getDataWithCallback(id, (error, data) => {
      if (error) {
        reject(error);
      } else {
        resolve(data);
      }
    });
  });
}
 
// Использование
getDataWithPromise(42)
  .then(data => console.log('Данные:', data))
  .catch(error => console.error('Ошибка:', error));
Можно создать универсальную функцию промисификации для колбэков, следующих шаблону error-first (где первый аргумент — ошибка, а второй — результат):

JavaScript
1
2
3
4
5
6
7
8
9
10
11
12
13
14
function promisify(fn) {
  return function(...args) {
    return new Promise((resolve, reject) => {
      fn(...args, (err, result) => {
        if (err) reject(err);
        else resolve(result);
      });
    });
  };
}
 
// Использование
const getDataPromised = promisify(getDataWithCallback);
getDataPromised(123).then(data => console.log(data));
В Node.js с версии 8 доступен встроенный метод util.promisify(), который выполняет подобное преобразование.

Антипаттерны при работе с промисами



Несмотря на все преимущества, промисы часто используются неправильно. Вот распространенные антипаттерны и способы их избежать:

1. Потеря возвращаемого промиса



JavaScript
1
2
3
4
5
6
7
8
9
10
11
12
13
14
// Неправильно:
function fetchData() {
  fetch('/api/data')  // Этот промис никуда не возвращается!
    .then(response => response.json())
    .then(data => {
      return data;  // Этот return бесполезен
    });
}
 
// Правильно:
function fetchData() {
  return fetch('/api/data')
    .then(response => response.json());
}

2. Ненужная вложенность промисов (Promise Hell)



JavaScript
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
// Неправильно:
fetchUser(userId)
  .then(user => {
    fetchPosts(user.id)
      .then(posts => {
        fetchComments(posts[0].id)
          .then(comments => {
            console.log(comments);
          });
      });
  });
 
// Правильно - используйте цепочку промисов:
fetchUser(userId)
  .then(user => fetchPosts(user.id))
  .then(posts => fetchComments(posts[0].id))
  .then(comments => console.log(comments));

3. Игнорирование ошибок



JavaScript
1
2
3
4
5
6
7
// Неправильно:
fetchData().then(data => processData(data));
 
// Правильно:
fetchData()
  .then(data => processData(data))
  .catch(error => console.error('Ошибка при получении или обработке данных:', error));

4. Создание ненужных промисов



JavaScript
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
// Неправильно:
function processData(data) {
  return new Promise((resolve, reject) => {
    resolve(data.map(item => item.value * 2));
  });
}
 
// Правильно:
function processData(data) {
  return Promise.resolve(data.map(item => item.value * 2));
}
 
// Или еще проще:
function processData(data) {
  return data.map(item => item.value * 2);
  // .then автоматически обернет результат в промис
}

Обработка таймаутов и отмена длительных промисов



JavaScript не предоставляет встроенного механизма для отмены промисов, но существуют паттерны, позволяющие эмулировать это поведение. Популярный подход — использование Promise.race() с таймаут-промисом:

JavaScript
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
function fetchWithTimeout(url, timeout = 5000) {
  const timeoutPromise = new Promise((_, reject) => {
    setTimeout(() => reject(new Error(`Запрос к ${url} превысил лимит ожидания ${timeout}мс`)), timeout);
  });
  
  return Promise.race([
    fetch(url),
    timeoutPromise
  ]);
}
 
fetchWithTimeout('/api/data', 3000)
  .then(response => response.json())
  .then(data => console.log('Данные получены вовремя:', data))
  .catch(error => console.error('Ошибка или таймаут:', error.message));
В современном JavaScript появился AbortController, который позволяет прерывать fetch-запросы и другие асинхронные операции:

JavaScript
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
function fetchWithAbort(url, timeout = 5000) {
  const controller = new AbortController();
  const { signal } = controller;
  
  // Устанавливаем таймер для прерывания запроса
  const timer = setTimeout(() => controller.abort(), timeout);
  
  return fetch(url, { signal })
    .then(response => {
      clearTimeout(timer); // Очищаем таймер при успешном запросе
      return response.json();
    })
    .catch(error => {
      if (error.name === 'AbortError') {
        throw new Error(`Запрос к ${url} был прерван по таймауту`);
      }
      throw error;
    });
}

Микротаски и макротаски: промисы в Event Loop



Понимание того, как промисы взаимодействуют с Event Loop, критически важно для предсказуемой работы асинхронного кода. Колбэки промисов (функции, переданные в .then(), .catch() и .finally()) выполняются как микротаски.
Микротаски имеют приоритет над макротасками (setTimeout, setInterval, I/O) и выполняются сразу после текущего синхронного кода, но до любой другой макротаски:

JavaScript
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
console.log('Синхронный код 1');
 
setTimeout(() => console.log('Таймер 1 (макротаска)'), 0);
 
Promise.resolve()
  .then(() => console.log('Промис 1 (микротаска)'))
  .then(() => console.log('Промис 2 (микротаска)'));
 
console.log('Синхронный код 2');
 
// Вывод:
// Синхронный код 1
// Синхронный код 2
// Промис 1 (микротаска)
// Промис 2 (микротаска)
// Таймер 1 (макротаска)
Этот порядок гарантирует, что все микротаски, добавленные во время выполнения текущего кода, будут обработаны до перехода к следующей макротаске. Это ключевая деталь, которая делает поведение промисов предсказуемым и позволяет системе оставаться в консистентном состоянии.

Async/Await - синтаксический сахар для промисов



В 2017 году с появлением стандарта ES2017 JavaScript получил одно из самых долгожданных обновлений - синтаксис async/await. Это стало очередным шагом в эволюции асинхронного программирования после введения промисов. Фактически async/await не добавляет новой функциональности к языку, а предоставляет более элегантный способ работы с существующими промисами.

Преимущества синтаксиса async/await



Ключевое преимущество async/await заключается в том, что асинхронный код выглядит и читается почти так же, как синхронный. Сравните два эквивалентных фрагмента кода:

JavaScript
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
// Использование промисов
function getUserData() {
  return fetchUser(userId)
    .then(user => {
      return fetchPosts(user.id)
        .then(posts => {
          return {
            user,
            posts
          };
        });
    });
}
 
// Использование async/await
async function getUserData() {
  const user = await fetchUser(userId);
  const posts = await fetchPosts(user.id);
  return { user, posts };
}
Вторая версия отличается большей читаемостью и меньшей вложенностью. Она позволяет писать асинхронный код, который выглядит почти как обычный синхронный JavaScript.

Основы использования async/await



Ключевое слово async объявляет функцию асинхронной. Такая функция всегда возвращает промис, даже если в теле функции не используется await:

JavaScript
1
2
3
4
5
6
7
8
9
10
async function hello() {
  return "Привет, мир!";
}
 
// Эквивалентно
function hello() {
  return Promise.resolve("Привет, мир!");
}
 
hello().then(message => console.log(message)); // "Привет, мир!"
Ключевое слово await можно использовать только внутри функций, обьявленных с async. Оно приостанавливает выполнение функции до тех пор, пока промис не выполнится (resolve) или не будет отклонён (reject).

JavaScript
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
async function showUserPosts() {
  try {
    // Ждём выполнения первого промиса
    const user = await fetchUser(userId);
    
    // Только после получения пользователя запрашиваем его посты
    const posts = await fetchPosts(user.id);
    
    // Отображаем результаты
    displayUserInfo(user);
    displayPosts(posts);
  } catch (error) {
    // Обработка любых ошибок
    showError("Не удалось загрузить данные", error);
  }
}
Важно помнить, что await не блокирует выполнение всей программы или JavaScript-потока — он приостанавливает только выполнение самой асинхронной функции, позволяя другому коду выполняться в это время.

Работа с ошибками через try/catch



Одним из наиболее значимых преимуществ async/await является возможность использовать стандартные конструкции try/catch для обработки ошибок, что делает код ещё более похожим на синхронный:

JavaScript
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
// Обработка ошибок с промисами
function getUserData() {
  return fetchUser(userId)
    .then(user => fetchPosts(user.id))
    .then(posts => {
      // Логика обработки данных
      return processData(posts);
    })
    .catch(error => {
      console.error("Ошибка:", error);
      return defaultData;
    });
}
 
// Обработка ошибок с async/await
async function getUserData() {
  try {
    const user = await fetchUser(userId);
    const posts = await fetchPosts(user.id);
    
    // Логика обработки данных
    return processData(posts);
  } catch (error) {
    console.error("Ошибка:", error);
    return defaultData;
  }
}
В этом примере блок try/catch перехватывает ошибки от обоих await-выражений, а также любые исключения, которые могут возникнуть в процессе обработки данных.
Если нужно обработать ошибки отдельно для каждой операции, можно использовать несколько блоков try/catch:

JavaScript
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
async function getUserData() {
  let user;
  try {
    user = await fetchUser(userId);
  } catch (error) {
    console.error("Ошибка при получении данных пользователя:", error);
    user = getDefaultUser();
  }
  
  try {
    const posts = await fetchPosts(user.id);
    return processData(posts);
  } catch (error) {
    console.error("Ошибка при получении постов:", error);
    return getDefaultPosts();
  }
}

Рефакторинг промис-кода в async/await



Преобразование существующего кода, использующего промисы, в код с async/await — относительно простой процесс, который может значительно улучшить читаемость:
1. Добавьте ключевое слово async перед определением функции.
2. Замените цепочки .then() на последовательность выражений с await.
3. Замените .catch() на блок try/catch.

Пример:

JavaScript
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
// До
function loadArticle(id) {
  return fetchArticle(id)
    .then(article => {
      return fetchComments(article.id)
        .then(comments => {
          article.comments = comments;
          return fetchAuthor(article.authorId)
            .then(author => {
              article.author = author;
              return article;
            });
        });
    })
    .catch(error => {
      console.error("Ошибка загрузки статьи:", error);
      throw error;
    });
}
 
// После
async function loadArticle(id) {
  try {
    const article = await fetchArticle(id);
    const comments = await fetchComments(article.id);
    article.comments = comments;
    const author = await fetchAuthor(article.authorId);
    article.author = author;
    return article;
  } catch (error) {
    console.error("Ошибка загрузки статьи:", error);
    throw error;
  }
}

Параллельное и последовательное выполнение асинхронных операций



Одна из распространенных ошибок при работе с async/await — ненужное последовательное выполнение независимых операций:

JavaScript
1
2
3
4
5
6
7
// Неэффективно: последовательное выполнение
async function loadData() {
  const users = await fetchUsers();
  const posts = await fetchPosts(); // Ждёт завершения fetchUsers
  const comments = await fetchComments(); // Ждёт завершения fetchPosts
  return { users, posts, comments };
}
Если операции не зависят друг от друга, лучше запустить их параллельно:

JavaScript
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
// Эффективно: параллельное выполнение независимых запросов
async function loadData() {
  const usersPromise = fetchUsers();
  const postsPromise = fetchPosts();
  const commentsPromise = fetchComments();
  
  // Ждём выполнения всех промисов
  const users = await usersPromise;
  const posts = await postsPromise;
  const comments = await commentsPromise;
  
  return { users, posts, comments };
}
 
// Или ещё компактнее с использованием Promise.all
async function loadData() {
  const [users, posts, comments] = await Promise.all([
    fetchUsers(),
    fetchPosts(),
    fetchComments()
  ]);
  
  return { users, posts, comments };
}

Особенности работы await в циклах



При использовании await в циклах нужно быть особенно внимательным. В зависимости от задачи может потребоваться последовательное или параллельное выполнение:

JavaScript
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
// Последовательное выполнение (каждый запрос ждёт завершения предыдущего)
async function fetchAllUserData(userIds) {
  const results = [];
  for (const id of userIds) {
    const userData = await fetchUserData(id);
    results.push(userData);
  }
  return results;
}
 
// Параллельное выполнение (все запросы запускаются одновременно)
async function fetchAllUserData(userIds) {
  const promises = userIds.map(id => fetchUserData(id));
  return Promise.all(promises);
}
Оба подхода имеют свои преимущества и недостатки. Последовательное выполнение может быть необходимо, если каждый запрос зависит от результатов предыдущего или если нужно контролировать нагрузку на сервер. Параллельное выполнение обычно значительно быстрее, но может создавать избыточную нагрузку.

Асинхронные генераторы и итераторы



С появлением async/await в JavaScript также были введены асинхронные генераторы и итераторы, позволяющие создавать потоки асинхронных данных:

JavaScript
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
// Асинхронный генератор
async function* asyncGenerator() {
  yield await Promise.resolve(1);
  yield await Promise.resolve(2);
  yield await Promise.resolve(3);
}
 
// Использование асинхронного генератора
async function consumeGenerator() {
  for await (const value of asyncGenerator()) {
    console.log(value);
  }
}
 
// Результат: 1, 2, 3
Асинхронные генераторы особенно полезны при работе с большими объемами данных, которые поступают частями, например при пагинации API-запросов или потоковой обработке данных.

Top-level await: модули без колбэков и IIFE



JavaScript долгое время не позволял использовать await вне асинхронных функций — приходилось оборачивать код в IIFE (Immediately Invoked Function Expression) или создавать искусственные асинхронные контексты. К счастью, с появлением top-level await в ECMAScript 2022 эта проблема решена для ES-модулей:

JavaScript
1
2
3
4
5
6
7
8
9
10
11
12
// Раньше приходилось писать так:
(async function() {
  const response = await fetch('/api/config');
  const config = await response.json();
  initApp(config);
})();
 
// Теперь можно просто:
// файл: app.js (с типом module)
const response = await fetch('/api/config');
const config = await response.json();
initApp(config);
Top-level await работает только внутри модулей (скриптов с type="module") и не поддерживается в обычных скриптах. Он заставляет модуль вести себя как большая асинхронная функция — импорт такого модуля другими модулями ожидает завершения всех top-level await операций. Эта функциональность открывает новые возможности:

JavaScript
1
2
3
4
5
6
7
// config.js
export const settings = await (await fetch('/api/settings')).json();
 
// dataService.js
import { settings } from './config.js';
// Здесь уже доступны настройки, загруженные в config.js
console.log(settings.apiKey);
Top-level await особенно полезен для инициализации приложения, когда перед запуском требуется загрузить конфигурацию или выполнить другие асинхронные действия.

Оптимизация производительности с async/await



Несмотря на удобство, неправильное использование async/await может снизить производительность. Вот несколько ключевых советов:

1. Избегайте блокирования UI-потока



Даже с async/await JavaScript остаётся однопоточным. Если асинхронная функция содержит тяжёлые вычисления, интерфейс всё равно "замёрзнет":

JavaScript
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
// Плохо: блокирует поток
async function processData() {
  const data = await fetchLargeDataset();
  
  // Тяжёлые вычисления, блокирующие рендеринг
  for (let i = 0; i < 1000000; i++) {
    data.items[i] = performComplexCalculation(data.items[i]);
  }
  
  return data;
}
 
// Лучше: разбить на порции с использованием setTimeout
async function processDataChunked() {
  const data = await fetchLargeDataset();
  const chunkSize = 1000;
  
  for (let i = 0; i < data.items.length; i += chunkSize) {
    await new Promise(resolve => {
      setTimeout(() => {
        processChunk(data.items, i, Math.min(i + chunkSize, data.items.length));
        resolve();
      }, 0);
    });
  }
  
  return data;
}

2. Кэширование результатов асинхронных операций



Повторные идентичные запросы можно оптимизировать с помощью кэширования:

JavaScript
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
// Простая реализация кэширования с помощью замыкания
function createAsyncCache(fetchFn) {
  const cache = new Map();
  
  return async function(key) {
    if (cache.has(key)) {
      return cache.get(key);
    }
    
    const result = await fetchFn(key);
    cache.set(key, result);
    return result;
  };
}
 
// Использование
const cachedFetchUser = createAsyncCache(fetchUser);
 
// Первый вызов — реальный запрос
const user1 = await cachedFetchUser(42);
 
// Второй вызов — мгновенный результат из кэша
const user2 = await cachedFetchUser(42);

3. Расстановка приоритетов для асинхронных операций



Не все асинхронные операции одинаково важны. Для критических операций можно использовать специальные техники:

JavaScript
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
async function loadPageData() {
  // Критически важные данные загружаем первыми
  const criticalData = await fetchCriticalData();
  renderCriticalUI(criticalData);
  
  // Второстепенные данные загружаем параллельно
  const [secondaryData, analytics, translations] = await Promise.all([
    fetchSecondaryData(),
    fetchAnalytics(),
    fetchTranslations()
  ]);
  
  // Обновляем UI по мере получения данных
  renderSecondaryUI(secondaryData);
  initAnalytics(analytics);
  applyTranslations(translations);
}

Дебаггинг асинхронного кода с async/await



Отладка асинхронного кода заметно упростилась с появлением async/await. Современные браузеры и Node.js предоставляют улучшенные инструменты для работы с асинхронным стеком вызовов.

Улучшенный стек ошибок



В старом промис-коде стеки ошибок часто были бесполезны, показывая только внутренности механизма промисов:

JavaScript
1
2
3
4
5
6
7
8
9
10
11
// Промис-код с малоинформативным стеком ошибок
function fetchData() {
  return fetch('/api/data')
    .then(response => response.json())
    .then(data => processData(data)); // Ошибка здесь
}
 
function processData(data) {
  // Стек не покажет связь с fetchData
  return data.nonExistentProperty.value;
}
С async/await стек ошибок содержит больше контекста:

JavaScript
1
2
3
4
5
6
7
8
9
10
// Async/await код с информативным стеком ошибок
async function fetchData() {
  const response = await fetch('/api/data');
  const data = await response.json();
  return processData(data); // Видно в стеке
}
 
function processData(data) {
  return data.nonExistentProperty.value;
}

Отладка с помощью точек останова



Для функций с async/await можно использовать обычные точки останова в любом месте кода, включая строки с await. Это значительно упрощает отладку по сравнению с цепочками промисов:

JavaScript
1
2
3
4
5
6
7
8
9
async function complexOperation() {
  const userData = await fetchUser();
  // Можно поставить точку останова здесь
  const filteredData = filterUserData(userData);
  
  const enrichedData = await enrichData(filteredData);
  // И здесь
  return transformData(enrichedData);
}

Ограничения async/await



Несмотря на все преимущества, async/await имеет ряд ограничений и особенностей, о которых важно знать:
1. Невозможность отмены — нет встроенного механизма для прерывания асинхронной операции (хотя AbortController может использоваться с fetch).
2. Потенциальное снижение производительности — из-за дополнительных промисов и прыжков по асинхронному стеку.
3. Проблемы с контекстом this — как и любые функции, async-функции могут терять контекст:

JavaScript
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
class DataService {
  constructor(baseUrl) {
    this.baseUrl = baseUrl;
    
    // Необходимо привязать контекст
    this.fetchData = this.fetchData.bind(this);
  }
  
  async fetchData(endpoint) {
    // this.baseUrl будет undefined без привязки
    const url = `${this.baseUrl}/${endpoint}`;
    const response = await fetch(url);
    return response.json();
  }
}

Мониторинг асинхронных операций



В больших приложениях часто нужно отслеживать выполнение многочисленных асинхронных операций. Полезно создать обёртки, отображающие прогресс и отслеживающие метрики:

JavaScript
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
async function trackedFetch(url, options = {}) {
  const start = performance.now();
  
  try {
    showLoader(url);
    
    const response = await fetch(url, options);
    const data = await response.json();
    
    const duration = performance.now() - start;
    logMetric('fetch-duration', { url, duration, success: true });
    
    return data;
  } catch (error) {
    const duration = performance.now() - start;
    logMetric('fetch-duration', { url, duration, success: false, error: error.message });
    
    throw error;
  } finally {
    hideLoader(url);
  }
}
Такой подход позволяет централизованно внедрять логирование, обработку ошибок и визуализацию состояния асинхронных операций.

В мире современной веб-разработки async/await стал предпочтительным способом работы с асинхронным кодом. Его синтаксическая простота, похожая на синхронный код, удобство отладки и обработки ошибок делают его незаменимым инструментом каждого JavaScript-разработчика. При этом помните, что под капотом async/await — всё те же промисы, понимание которых остаётся критически важным.

Fetch API - современный подход к сетевым запросам



После освоения промисов и синтаксиса async/await логичным шагом становится изучение Fetch API — современного интерфейса для выполнения HTTP-запросов, который пришёл на смену устаревшему XMLHttpRequest.
Fetch API предоставляет мощный и гибкий механизм для взаимодействия с сетевыми ресурсами. Его ключевая особенность в том, что он полностью основан на промисах, что делает его идеальным компаньоном для async/await синтаксиса.

Базовое использование Fetch API



В простейшем случае функция fetch() принимает URL ресурса, который необходимо загрузить:

JavaScript
1
2
3
4
fetch('https://api.example.com/data')
  .then(response => response.json())
  .then(data => console.log(data))
  .catch(error => console.error('Ошибка:', error));
С использованием async/await тот же код выглядит ещё элегантнее:

JavaScript
1
2
3
4
5
6
7
8
9
async function fetchData() {
  try {
    const response = await fetch('https://api.example.com/data');
    const data = await response.json();
    console.log(data);
  } catch (error) {
    console.error('Ошибка:', error);
  }
}
При вызове fetch() возвращается промис, который разрешается объектом Response, как только сервер отвечает заголовками — даже если ответ представляет собой ошибку HTTP (например, 404 или 500). Промис будет отклонён только при сетевых ошибках, например при отсутствии соединения.

Работа с JSON и другими форматами данных



Объект Response предоставляет несколько методов для обработки ответов разных типов:
.json(): преобразует ответ в JSON,
.text(): получает ответ в виде текста,
.blob(): обрабатывает бинарные данные, например изображения,
.arrayBuffer(): для низкоуровневой обработки бинарных данных,
.formData(): для работы с данными в формате FormData.
Каждый из этих методов возвращает промис с соответствующим результатом:

JavaScript
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
// Получение и обработка изображения
async function fetchImage() {
  const response = await fetch('https://example.com/image.jpg');
  const imageBlob = await response.blob();
  const imageURL = URL.createObjectURL(imageBlob);
  
  const imgElement = document.createElement('img');
  imgElement.src = imageURL;
  document.body.appendChild(imgElement);
}
 
// Получение текстового файла
async function fetchTextFile() {
  const response = await fetch('https://example.com/data.txt');
  const text = await response.text();
  console.log(text);
}

Настройка заголовков и параметров запроса



Для более сложных запросов fetch() принимает второй параметр — объект опций. Он позволяет настроить метод запроса, заголовки, тело и другие параметры:

JavaScript
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
async function postData(url, data) {
  const response = await fetch(url, {
    method: 'POST',
    headers: {
      'Content-Type': 'application/json',
      'Authorization': 'Bearer ' + token
    },
    body: JSON.stringify(data)
  });
  
  return response.json();
}
 
// Использование
const userData = { name: 'Иван', email: 'ivan@example.com' };
const result = await postData('/api/users', userData);
Для работы с заголовками Fetch API предоставляет специальный класс Headers, который упрощает манипуляции с HTTP-заголовками:

JavaScript
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
const headers = new Headers();
headers.append('Content-Type', 'application/json');
headers.append('X-Custom-Header', 'custom-value');
 
// Проверка наличия заголовка
if (headers.has('Content-Type')) {
  console.log('Заголовок Content-Type установлен');
}
 
// Получение значения заголовка
console.log(headers.get('X-Custom-Header')); // 'custom-value'
 
// Использование в fetch
fetch('/api/data', {
  headers: headers
});

Отправка данных формы



Fetch API предоставляет удобные способы для работы с формами. Можно отправить данные формы с помощью объекта FormData:

JavaScript
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
async function submitForm(formElement) {
  const formData = new FormData(formElement);
  
  // Можно добавить дополнительные поля
  formData.append('timestamp', Date.now());
  
  const response = await fetch('/api/submit', {
    method: 'POST',
    body: formData
    // Заголовок Content-Type устанавливается автоматически
  });
  
  return response.json();
}
 
// Использование
const form = document.querySelector('#contact-form');
form.addEventListener('submit', async function(event) {
  event.preventDefault();
  const result = await submitForm(this);
  showSuccessMessage(result);
});

Проверка статуса ответа



Важной особенностью Fetch API является то, что промис не отклоняется при ошибках HTTP, таких как 404 или 500. Вместо этого необходимо явно проверять свойство ok или код статуса ответа:

JavaScript
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
async function fetchWithErrorHandling(url) {
  const response = await fetch(url);
  
  if (!response.ok) {
    // Создаём ошибку с информацией о статусе
    throw new Error(`HTTP ошибка! Статус: ${response.status}`);
  }
  
  return await response.json();
}
 
// Альтернативный подход с проверкой конкретных статусов
async function fetchWithStatusCheck(url) {
  const response = await fetch(url);
  
  if (response.status === 404) {
    throw new Error('Ресурс не найден');
  } else if (response.status === 401) {
    redirectToLogin();
    throw new Error('Требуется авторизация');
  } else if (!response.ok) {
    throw new Error('Ошибка сети');
  }
  
  return await response.json();
}

Отмена запросов с помощью AbortController



В современном JavaScript появилась возможность отменять выполняющиеся fetch-запросы с помощью интерфейса AbortController:

JavaScript
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
async function fetchWithTimeout(url, timeout = 5000) {
  // Создаём контроллер для возможности отмены
  const controller = new AbortController();
  const { signal } = controller;
  
  // Устанавливаем таймер для автоматической отмены
  const timeoutId = setTimeout(() => controller.abort(), timeout);
  
  try {
    const response = await fetch(url, { signal });
    clearTimeout(timeoutId); // Отменяем таймаут при успешном ответе
    
    if (!response.ok) {
      throw new Error(`HTTP ошибка: ${response.status}`);
    }
    
    return await response.json();
  } catch (error) {
    if (error.name === 'AbortError') {
      throw new Error(`Запрос к ${url} был отменён из-за превышения таймаута`);
    }
    throw error;
  }
}
 
// Пример ручной отмены
const controller = new AbortController();
const { signal } = controller;
 
const fetchPromise = fetch('/api/large-data', { signal });
 
// Отмена при клике на кнопку
document.querySelector('#cancel-button').addEventListener('click', () => {
  controller.abort();
});

Сравнение Fetch API с XMLHttpRequest и Axios



Fetch API имеет ряд отличий от старого доброго XMLHttpRequest:
1. Промисы вместо колбэков — Fetch возвращает промисы, что делает работу с асинхронным кодом намного элегантнее.
2. Более простой и чистый API — сравните:

JavaScript
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
// XMLHttpRequest
const xhr = new XMLHttpRequest();
xhr.open('GET', '/api/data', true);
xhr.onreadystatechange = function() {
  if (xhr.readyState === 4 && xhr.status === 200) {
    const data = JSON.parse(xhr.responseText);
    console.log(data);
  }
};
xhr.send();
 
// Fetch
fetch('/api/data')
  .then(response => response.json())
  .then(data => console.log(data));
3. Нет встроенного таймаута — в Fetch для этого нужно использовать AbortController.
4. Нет автоматической отправки/получения куки по умолчанию между доменами — это требует дополнительной настройки с параметром credentials.
В то же время популярная библиотека Axios предлагает некоторые возможности, отсутствующие в нативном Fetch:

JavaScript
1
2
3
4
5
6
7
8
9
10
11
12
// Axios автоматически преобразует JSON и имеет встроенную проверку статусов
axios.get('/api/data')
  .then(response => console.log(response.data)) // Сразу получаем данные
  .catch(error => console.error('Ошибка:', error)); // Автоматически отклоняется при ошибках HTTP
 
// Встроенный мониторинг прогресса загрузки
axios.get('/api/large-file', {
  onDownloadProgress: progressEvent => {
    const percentCompleted = Math.round((progressEvent.loaded * 100) / progressEvent.total);
    console.log(`Загружено: ${percentCompleted}%`);
  }
});
Несмотря на преимущества Axios, нативный Fetch API постоянно совершенствуется и имеет несколько важных достоинств:
1. Встроенность в браузер — не требует подключения внешних библиотек.
2. Меньший размер бандла — особенно важно для мобильных приложений.
3. Следование стандартам — использует современные концепции веб-платформы.
4. Лучшая интеграция с другими современными API — например, со Streams API.

Загрузка файлов и отслеживание прогресса



Одним из ограничений Fetch API долгое время было отсутствие нативной поддержки для отслеживания прогресса загрузки. Однако современные браузеры получили интеграцию с Streams API, что позволяет реализовать эту функциональность:

JavaScript
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
async function downloadWithProgress(url) {
  const response = await fetch(url);
  const reader = response.body.getReader();
  
  // Получаем общий размер
  const contentLength = +response.headers.get('Content-Length');
  
  // Читаем данные по частям
  let receivedLength = 0;
  let chunks = [];
  
  while(true) {
    const {done, value} = await reader.read();
    
    if (done) {
      break;
    }
    
    chunks.push(value);
    receivedLength += value.length;
    
    // Вычисляем и выводим прогресс
    const progress = ((receivedLength / contentLength) * 100).toFixed(2);
    console.log(`Загружено: ${progress}%`);
  }
  
  // Объединяем все фрагменты в единый массив
  const allChunks = new Uint8Array(receivedLength);
  let position = 0;
  
  for(const chunk of chunks) {
    allChunks.set(chunk, position);
    position += chunk.length;
  }
  
  return allChunks;
}

Обработка CORS и кросс-доменные запросы



CORS (Cross-Origin Resource Sharing) — механизм, позволяющий запрашивать ресурсы с домена, отличного от домена, с которого был загружен скрипт. Fetch по умолчанию соблюдает правила CORS:

JavaScript
1
2
3
4
5
6
7
8
9
10
11
12
13
14
// Запрос к другому домену
fetch('https://api.другой-сайт.com/data', {
  // Важный параметр для кросс-доменных запросов с аутентификацией
  credentials: 'include', // Отправляет куки на другой домен
  
  // Также можно использовать 'same-origin' или 'omit'
})
.then(response => response.json())
.catch(error => {
  // Обработка CORS-ошибок
  if (error instanceof TypeError && error.message.includes('CORS')) {
    console.error('CORS-ошибка: сервер не разрешает кросс-доменные запросы');
  }
});
Для решения проблем с CORS можно:
1. Настроить правильные заголовки на сервере.
2. Использовать прокси на своём сервере.
3. Применять режим 'no-cors' (с серьёзными ограничениями доступа к ответу).

Streaming API и потоковая обработка данных



Интеграция Fetch с Streams API позволяет обрабатывать большие объёмы данных по мере их поступления, не дожидаясь загрузки всего ресурса:

JavaScript
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
// Потоковая обработка текстовых данных
async function streamProcess(url) {
  const response = await fetch(url);
  const reader = response.body
    .pipeThrough(new TextDecoderStream())
    .getReader();
  
  while (true) {
    const {value, done} = await reader.read();
    if (done) break;
    
    // Обрабатываем каждый фрагмент данных
    processTextChunk(value);
  }
}

Мокирование Fetch API для тестирования



Для эффективного юнит-тестирования кода, использующего Fetch API, часто применяется мокирование:

JavaScript
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
// Использование библиотеки jest-fetch-mock
global.fetch = require('jest-fetch-mock');
 
// Настройка мока
fetch.mockResponseOnce(JSON.stringify({ data: 'mock data' }));
 
// Тестируемая функция
async function getData() {
  const response = await fetch('/api/data');
  return response.json();
}
 
// Тест
test('getData должна вернуть данные', async () => {
  const data = await getData();
  expect(data).toEqual({ data: 'mock data' });
  expect(fetch).toHaveBeenCalledWith('/api/data');
});
Fetch API продолжает развиваться, получая новые возможности в каждой версии браузеров. Современная разработка веб-приложений практически невозможна без использования этого инструмента, делающего работу с сетевыми запросами элегантной и эффективной.

Вернуть данные из промиса async/await
Как вернуть строку их промиса? function allLeagues(){ let ids = ; fetch(link) ...

Промисы async/await
Всем привет! Помогите разобраться? Делал когда-то приложение на сервере MAMP со старыми версиями...

Не могу понять как работают промисы async/await
Не могу понять как работают промисы, есть у меня экспортируемая функция которая отправляет запрос...

Вернуть значение из промиса async/await
function getData() { fetch(`${PARAMS.authEndpoint}/v2/token`, { method: 'POST',...

Область видимости, промисы async/await
Здравствуйте! Помогите пожалуйста разобраться, как работать с объектом? const rates = {}; ...

Как дождаться выполнения всех промисов? async/await
Добрый вечер! В функции, которая не является async (делать ее таково йне предполагается) делаются...

Промисы async/await
Добрый день! У меня есть такая задача, вроде бы простейшая, но я не могу разобраться. Я работаю с...

Анимация круга с помощью промиса async/await
Написать функцию showCircle возвращающую промис и рисующая постепенно растущий круг. &lt;!DOCTYPE...

Написать что-то вроде асинхронного мэпа, который возвращает промис
Всем привет! Необходимо написать что-то вроде асинхронного мэпа, который возвращает промис и имеет...

Как верно использовать JavaScript native Fetch API вместо jQuery для опроса данных из API
Я разбираюсь в js, и на данный момент понял что метод из js Fetch может сам отправлять запросы на...

Как корректно заменить async: false на async: true
Проблема в том, что данный код устарел, блокирует браузер на время выполнения скрипта и выдает...

Асинхронный chrome.runtime.sendMessage и fetch
Здравствуйте. Делаю расширение. Отправляю из &quot;default_popup&quot; запрос (sendMessage) в фоновую...

Надоела реклама? Зарегистрируйтесь и она исчезнет полностью.
Всего комментариев 0
Комментарии
 
Новые блоги и статьи
Генераторы Python для эффективной обработки данных
AI_Generated 21.05.2025
В Python существует инструмент настолько мощный и в то же время недооценённый, что я часто сравниваю его с тайным оружием в арсенале программиста. Речь идёт о генераторах — одной из самых элегантных. . .
Чем заменить Swagger в .NET WebAPI
stackOverflow 21.05.2025
Если вы создавали Web API на . NET в последние несколько лет, то наверняка сталкивались с зелёным интерфейсом Swagger UI. Этот инструмент стал практически стандартом для документирования и. . .
Использование Linq2Db в проектах C# .NET
UnmanagedCoder 21.05.2025
Среди множества претендентов на корону "идеального ORM" особое место занимает Linq2Db — микро-ORM, балансирующий между мощью полноценных инструментов и легковесностью ручного написания SQL. Что. . .
Реализация Domain-Driven Design с Java
Javaican 20.05.2025
DDD — это настоящий спасательный круг для проектов со сложной бизнес-логикой. Подход, предложенный Эриком Эвансом, позволяет создавать элегантные решения, которые точно отражают реальную предметную. . .
Возможности и нововведения C# 14
stackOverflow 20.05.2025
Выход версии C# 14, который ожидается вместе с . NET 10, приносит ряд интересных нововведений, действительно упрощающих жизнь разработчиков. Вы уже хотите опробовать эти новшества? Не проблема! Просто. . .
Собеседование по Node.js - вопросы и ответы
Reangularity 20.05.2025
Каждому разработчику рано или поздно приходится сталкиватся с техническими собеседованиями - этим стрессовым испытанием, где решается судьба карьерного роста и зарплатных ожиданий. В этой статье я. . .
Cython и C (СИ) расширения Python для максимальной производительности
py-thonny 20.05.2025
Python невероятно дружелюбен к начинающим и одновременно мощный для профи. Но стоит лишь заикнуться о высокопроизводительных вычислениях — и энтузиазм быстро улетучивается. Да, Питон медлительнее. . .
Безопасное программирование в Java и предотвращение уязвимостей (SQL-инъекции, XSS и др.)
Javaican 19.05.2025
Самые распространёные векторы атак на Java-приложения за последний год выглядят как классический "топ-3 хакерских фаворитов": SQL-инъекции (31%), межсайтовый скриптинг или XSS (28%) и CSRF-атаки. . .
Введение в Q# - язык квантовых вычислений от Microsoft
EggHead 19.05.2025
Microsoft вошла в гонку технологических гигантов с собственным языком программирования Q#, специально созданным для разработки квантовых алгоритмов. Но прежде чем погружаться в синтаксические дебри. . .
Безопасность Kubernetes с Falco и обнаружение вторжений
Mr. Docker 18.05.2025
Переход организаций к микросервисной архитектуре и контейнерным технологиям сопровождается лавинообразным ростом векторов атак — от тривиальных попыток взлома до многоступенчатых кибератак, способных. . .
КиберФорум - форум программистов, компьютерный форум, программирование
Powered by vBulletin
Copyright ©2000 - 2025, CyberForum.ru