Например,
await приостанавливает выполнение текущей async-функции до завершения Promise, а try...catch позволяет удобно обрабатывать возникающие ошибки.На картинке — основной синтаксис
async/await, принцип его работы, сравнение с цепочками .then() и другие полезные моменты.Сохрани, чтобы не потерять!
Please open Telegram to view this post
VIEW IN TELEGRAM
🔥9❤8👍7
Склеивайте множество синхронных обновлений в одно!
Иногда функция может вызываться десятки раз за один проход синхронного кода.
Можно накопить изменения и запланировать обработку один раз:
Теперь даже серия вызовов обработается одной пачкой:
🔥 Такой подход объединяет множество быстрых обновлений в одну обработку, уменьшая лишние вычисления и нагрузку. Что крайне полезно для событий и часто обновляемых данных.
📣 JS Ready | #совет
Иногда функция может вызываться десятки раз за один проход синхронного кода.
Можно накопить изменения и запланировать обработку один раз:
const pending = new Set();
function update(id) {
pending.add(id);
scheduleFlush();
}
queueMicrotask() выполнит flush после текущего синхронного кода, но до перехода к следующей macrotask:function flush() {
scheduled = false;
process([...pending]);
pending.clear();
}Теперь даже серия вызовов обработается одной пачкой:
update(1);
update(2);
update(3);
// process([1, 2, 3]) — один раз
Please open Telegram to view this post
VIEW IN TELEGRAM
🔥10❤6👍5
Например,
const [a, b] = arr позволяет получить значения из массива, а const { name, age } = user — извлечь нужные свойства из объекта.На картинке — основные способы деструктуризации массивов и объектов, которые пригодятся в повседневной разработке.
Сохрани, чтобы не потерять!
Please open Telegram to view this post
VIEW IN TELEGRAM
🔥12👍8❤3🤝2
Почему Array.sort() может создать баг далеко от места сортировки!
С
Особенно неприятно это становится на границе функций. По названию
В результате функция имеет побочный эффект, который не очевиден из места её вызова. Это уже может затронуть состояние, кэш, мемоизацию или другой код, использующий тот же массив:
Классический способ избежать этого — сначала создать новый массив и сортировать уже его. Такой код работает, но копирование приходится выражать отдельной операцией:
Важно, что копируется именно массив. Объекты внутри него не клонируются, поэтому обе коллекции по-прежнему содержат ссылки на одни и те же объекты:
Поэтому изменение самого объекта будет видно через обе коллекции:
Похожая логика есть у
🔥 Поэтому выбор между
📣 JS Ready | #практика
С
sort() проблема не в самой сортировке, а в скрытой мутации. Вызов выглядит как обычное получение отсортированного результата, хотя одновременно изменяет сам исходный массив:const scores = [30, 10, 20];
const sorted = scores.sort(
(a, b) => a - b
);
console.log(scores);
// [10, 20, 30]
sort() сортирует массив непосредственно и возвращает ту же ссылку. Поэтому изменение увидит не только код, работающий с sorted, но и любой код, который уже хранит ссылку на исходный массив:const scores = [30, 10, 20];
const cached = scores;
scores.sort((a, b) => a - b);
console.log(cached);
// [10, 20, 30]
Особенно неприятно это становится на границе функций. По названию
getSortedUsers() можно ожидать получение нового результата, но реализация незаметно меняет массив вызывающего кода:function getSortedUsers(users) {
return users.sort(
(a, b) => a.age - b.age
);
}В результате функция имеет побочный эффект, который не очевиден из места её вызова. Это уже может затронуть состояние, кэш, мемоизацию или другой код, использующий тот же массив:
const users = getUsers();
const original = users;
getSortedUsers(users);
console.log(original === users);
// true
Классический способ избежать этого — сначала создать новый массив и сортировать уже его. Такой код работает, но копирование приходится выражать отдельной операцией:
const sorted = [...scores].sort(
(a, b) => a - b
);
console.log(scores);
// [30, 10, 20]
toSorted() делает намерение явным: получить новый массив с другим порядком элементов, не изменяя исходный. При этом возвращается уже другая ссылка:const sorted = scores.toSorted(
(a, b) => a - b
);
console.log(scores);
// [30, 10, 20]
console.log(scores === sorted);
// false
Важно, что копируется именно массив. Объекты внутри него не клонируются, поэтому обе коллекции по-прежнему содержат ссылки на одни и те же объекты:
const users = [
{ name: "Alex", age: 30 },
{ name: "Bob", age: 20 },
];
const sorted = users.toSorted(
(a, b) => a.age - b.age
);
console.log(
sorted[0] === users[1]
);
// true
Поэтому изменение самого объекта будет видно через обе коллекции:
sorted[0].age = 25;
console.log(users[1].age);
// 25
Похожая логика есть у
toReversed() и toSpliced(): они позволяют выполнить соответствующую операцию без изменения исходного массива, в отличие от мутирующих reverse() и splice().sort() и toSorted() — это не просто вопрос синтаксиса, это выбор контракта операции: изменить существующий массив или получить новый, сохранив исходный. Чем больше у массива потребителей, тем важнее эта разница.Please open Telegram to view this post
VIEW IN TELEGRAM
🔥9👍6🤝4❤1
Метод
filter() используется для отбора элементов массива по заданному условию. Результатом выполнения является новый массив, при этом исходный массив не изменяется.На изображении — синтаксис метода, аргументы callback-функции и примеры фильтрации чисел и объектов.
Сохрани, чтобы не потерять!
Please open Telegram to view this post
VIEW IN TELEGRAM
👍8🔥5🤝3
Почему JavaScript может изменить большой ID из API
Числовой идентификатор может быть корректно сформирован на сервере, но изменить значение после преобразования в js
Причина заключается в модели представления чисел:
Граница гарантированно точного представления целых чисел определяется константой
Безопасный диапазон составляет от
Для точных операций с большими целыми в js существует
С API возникает дополнительная граница — десериализация JSON. Сам JSON может содержать последовательность цифр
Если значение находится за пределами безопасного целочисленного диапазона, точность теряется уже во время этого преобразования:
После такого парсинга восстановить исходный ID по полученному
Поэтому идентификаторы, над которыми не предполагается арифметика, надёжнее передавать в JSON как строки. Это сохраняет исходную последовательность цифр независимо от числовых ограничений JavaScript-клиента:
Если значение всё же требуется использовать в точных целочисленных вычислениях, строку можно явно преобразовать в
🔥 Числовая природа ID не означает, что его следует представлять типом
📣 JS Ready | #практика
Числовой идентификатор может быть корректно сформирован на сервере, но изменить значение после преобразования в js
Number. Причина заключается в модели представления чисел:
Number соответствует IEEE 754 binary64 и не способен точно представить все целые числа произвольного размера:const id = 9007199254740993;
console.log(id);
// 9007199254740992
Граница гарантированно точного представления целых чисел определяется константой
Number.MAX_SAFE_INTEGER:console.log(Number.MAX_SAFE_INTEGER);
// 9007199254740991
console.log(
Number.isSafeInteger(9007199254740991)
);
// true
Безопасный диапазон составляет от
-(2^53 - 1) до 2^53 - 1. За его пределами проблема заключается не просто в том, что число становится «слишком большим»: соседние математические целые уже не обязаны иметь разные представления в Number. Поэтому два разных исходных значения могут оказаться одним и тем же значением javascript:const a = 9007199254740992;
const b = 9007199254740993;
console.log(a === b);
// true
Для точных операций с большими целыми в js существует
BigInt. Суффикс n создаёт значение непосредственно как BigInt, без промежуточного преобразования в Number:const id = 9007199254740993n;
console.log(id + 1n);
// 9007199254740994n
С API возникает дополнительная граница — десериализация JSON. Сам JSON может содержать последовательность цифр
9007199254740993 как число, но стандартный JSON.parse() преобразует JSON-число в JavaScript Number. Если значение находится за пределами безопасного целочисленного диапазона, точность теряется уже во время этого преобразования:
const data = JSON.parse(
'{"id":9007199254740993}'
);
console.log(data.id);
// 9007199254740992
После такого парсинга восстановить исходный ID по полученному
Number в общем случае невозможно: информация о первоначальном целом уже потеряна.Поэтому идентификаторы, над которыми не предполагается арифметика, надёжнее передавать в JSON как строки. Это сохраняет исходную последовательность цифр независимо от числовых ограничений JavaScript-клиента:
const data = JSON.parse(
'{"id":"9007199254740993"}'
);
console.log(data.id);
// "9007199254740993"
Если значение всё же требуется использовать в точных целочисленных вычислениях, строку можно явно преобразовать в
BigInt уже после получения ответа API:const data = JSON.parse(
'{"id":"9007199254740993"}'
);
const id = BigInt(data.id);
console.log(id + 1n);
// 9007199254740994n
Number. Если идентификатор потенциально превышает Number.MAX_SAFE_INTEGER, его представление становится частью корректности API-контракта: строка сохраняет ID без потерь при передаче, а BigInt подходит там, где действительно требуется точная целочисленная арифметика.Please open Telegram to view this post
VIEW IN TELEGRAM
❤12👍8🔥6
Разбираем Web Workers, с помощью которых можно выполнять ресурсоёмкие задачи вне основного UI-потока: запускать фоновые вычисления, передавать данные между main thread и Worker, получать результаты, обрабатывать ошибки и управлять завершением Worker. Используется для тяжёлых вычислений и обработки больших объёмов данных без блокировки интерфейса.Please open Telegram to view this post
VIEW IN TELEGRAM
Please open Telegram to view this post
VIEW IN TELEGRAM
❤12👍9🔥5
Выполняйте независимые операции без эффекта домино!
Если несколько операций не зависят друг от друга,
Для независимых задач используйте
Что полезно для пакетной отправки, фоновых задач и загрузки независимых данных: ошибка одной операции не скрывает успешные результаты остальных.
🔥 Если операции независимы, не позволяйте одной ошибке скрывать результат остальных — обрабатывайте каждую отдельно.
📣 JS Ready | #совет
Если несколько операций не зависят друг от друга,
Promise.all() может создать лишнюю связанность: одна ошибка отклоняет общий Promise, хотя остальные задачи продолжат выполняться и могут успешно завершиться.const users = await Promise.all(
ids.map(id => loadUser(id))
);
Для независимых задач используйте
Promise.allSettled() — он дождётся каждой и сохранит результат отдельно:const results = await Promise.allSettled(
ids.map(id => loadUser(id))
);
const successful = results
.filter(r => r.status === 'fulfilled')
.map(r => r.value);
Что полезно для пакетной отправки, фоновых задач и загрузки независимых данных: ошибка одной операции не скрывает успешные результаты остальных.
Please open Telegram to view this post
VIEW IN TELEGRAM
🔥11👍5🤝3❤1
В этой статье:
• Показывается, как с нуля собрать браузерного тамагочи на HTML, CSS и JavaScript — без фреймворков, npm и сборщиков;• Разбираются игровое состояние, жизненный цикл питомца, случайные события, мини-игра, коллекция и сохранение прогресса через localStorage;• Объясняется, как рисовать пиксельных персонажей программно через Canvas и создавать звуки через Web Audio API без готовых изображений и аудиофайлов.🔊 Продолжайте читать на Habr!
Please open Telegram to view this post
VIEW IN TELEGRAM
❤9🔥7👍5🤝2
Например,
REST используют для построения API, WebSocket — для двустороннего обмена данными, а gRPC — для взаимодействия между сервисами.На картинке — основные технологии и подходы, с которыми регулярно сталкиваются разработчики: REST, Webhooks, GraphQL, SOAP, WebSocket, gRPC, MQTT, AMQP, SSE, EDI и EDA. Кратко и наглядно показано, чем они отличаются, как работают и для каких задач применяются.
Сохрани, чтобы не потерять!
Please open Telegram to view this post
VIEW IN TELEGRAM
👍8❤5🤝5🔥2
HTTP передаёт данные между клиентом и сервером без шифрования, а HTTPS добавляет TLS — он аутентифицирует сервер, согласовывает ключи и защищает дальнейший обмен данными шифрованием.
На картинке наглядно показано, чем отличается обычное HTTP-соединение от HTTPS: от TCP-handshake до установки защищённого соединения и передачи данных.
Сохрани, чтобы не потерять!
Please open Telegram to view this post
VIEW IN TELEGRAM
👍9❤5🔥4🤝3
Используйте возвращаемое значение delete() вместо лишней проверки!
У
Поэтому вместо двойного поиска:
можно сразу использовать результат удаления:
То же самое работает с
Вы не выполняете сначала
🔥 Не проверяйте коллекцию перед удалением, если проверка нужна только ради условия —
📣 JS Ready | #совет
У
Map.delete() и Set.delete() есть полезное свойство: метод возвращает true, только если элемент действительно существовал и был удалён.Поэтому вместо двойного поиска:
if (cache.has(key)) {
cache.delete(key);
onRemoved(key);
}можно сразу использовать результат удаления:
if (cache.delete(key)) {
onRemoved(key);
}То же самое работает с
Set:if (activeUsers.delete(userId)) {
updateCounter();
}Вы не выполняете сначала
has(), а затем отдельный delete(). Сама операция удаления одновременно отвечает на вопрос, был ли элемент.delete() уже возвращает нужный результат.Please open Telegram to view this post
VIEW IN TELEGRAM
👍8🔥8❤3🤝2
Подписывайтесь на событие только один раз через once!
У
Особенно полезно для одноразовых действий: завершения анимации, первого взаимодействия, загрузки ресурса или ожидания ответа.
Можно удобно превращать одноразовые события в
🔥 Для одноразового события используйте
📣 JS Ready | #совет
У
addEventListener() есть опция once: обработчик автоматически удаляется после первого срабатывания. Без ручного removeEventListener():button.addEventListener('click', handleClick, {
once: true
});Особенно полезно для одноразовых действий: завершения анимации, первого взаимодействия, загрузки ресурса или ожидания ответа.
element.addEventListener('transitionend', () => {
element.remove();
}, { once: true });Можно удобно превращать одноразовые события в
Promise:const once = (target, event) =>
new Promise(resolve =>
target.addEventListener(event, resolve, { once: true })
);
await once(video, 'ended');
console.log('Видео закончилось');
{ once: true }. Если ждёте одно из нескольких — снимайте оставшиеся обработчики через AbortController.Please open Telegram to view this post
VIEW IN TELEGRAM
👍11❤6🔥4
Как оплачивать зарубежные сервисы в 2026 году?
Можно бегать между посредниками и бояться блокировок после оплаты, а можно выпустить международную карту Lumio Pay и пользоваться любимыми сервисами без рисков.
— выпуск карты за 2 минуты
— лучший курс пополнения на рынке (у конкурентов на 20% выше)
— пополнение рублями или криптой
— чистые BIN карт, оплата без риска блокировок
Пока все ищут идеальное решение, оно у тебя перед глазами: @LumioPay
Можно бегать между посредниками и бояться блокировок после оплаты, а можно выпустить международную карту Lumio Pay и пользоваться любимыми сервисами без рисков.
— выпуск карты за 2 минуты
— лучший курс пополнения на рынке (у конкурентов на 20% выше)
— пополнение рублями или криптой
— чистые BIN карт, оплата без риска блокировок
Пока все ищут идеальное решение, оно у тебя перед глазами: @LumioPay
Почему forEach() не ждёт async-функцию?
Если операции должны выполняться последовательно, ожидание необходимо сделать частью самого цикла. Для этого подходит
Следующая итерация начнётся только после завершения предыдущей. Такой вариант нужен, когда важен порядок выполнения или операции зависят друг от друга.
Для независимых операций можно сформировать массив
Есть и важное следствие для обработки ошибок: внешний
Для управляемой обработки ошибки необходимо ожидать
🔥 Проблема не в
📣 JS Ready | #практика
forEach() не учитывает возвращаемое значение callback и не содержит механизма ожидания Promise. Поэтому передача async-callback не меняет модель выполнения метода: forEach() синхронно запускает callback для каждого элемента, не ожидая завершения созданных им асинхронных операций:const users = [1, 2, 3];
users.forEach(async (id) => {
await saveUser(id);
});
console.log("Готово");
"Готово" будет выведено до завершения saveUser(). Каждый вызов async-callback возвращает Promise, но forEach() эти Promise не ожидает и не собирает.Если операции должны выполняться последовательно, ожидание необходимо сделать частью самого цикла. Для этого подходит
for...of с await:for (const id of users) {
await saveUser(id);
}
console.log("Готово");Следующая итерация начнётся только после завершения предыдущей. Такой вариант нужен, когда важен порядок выполнения или операции зависят друг от друга.
Для независимых операций можно сформировать массив
Promise и дождаться их через Promise.all():await Promise.all(
users.map((id) => saveUser(id))
);
console.log("Готово");
Promise.all() успешно выполняется после выполнения всех переданных Promise и отклоняется, если хотя бы один из них завершается с ошибкой. При этом остальные уже запущенные операции автоматически не отменяются.Есть и важное следствие для обработки ошибок: внешний
try...catch вокруг forEach() не перехватит последующее отклонение Promise из async-callback:try {
users.forEach(async (id) => {
await saveUser(id);
});
} catch (error) {
console.error(error);
}Для управляемой обработки ошибки необходимо ожидать
Promise, представляющий выполнение асинхронных операций:try {
await Promise.all(
users.map((id) => saveUser(id))
);
} catch (error) {
console.error(error);
}async внутри forEach(), а в контракте самого метода: forEach() игнорирует возвращаемое значение callback и не ожидает Promise. Для последовательного выполнения используйте for...of с await, для конкурентного выполнения независимых операций — Promise.all().Please open Telegram to view this post
VIEW IN TELEGRAM
👍9❤6🔥6
Например,
REST используется для построения HTTP API, а OAuth 2.0 и JWT помогают организовать авторизацию и безопасную передачу данных.На картинке — основные темы API-разработки: методы и коды ответов, типы API, безопасность, документация, тестирование, интеграция и API Gateway.
Сохрани, чтобы не потерять!
Please open Telegram to view this post
VIEW IN TELEGRAM
🔥10👍8❤4🤝1
Защищаем состояние от устаревших асинхронных результатов!
Порядок запуска асинхронных операций не определяет порядок их завершения. Если несколько вызовов обновляют одно состояние, более ранний запрос может завершиться позже и перезаписать актуальные данные:
Если
Когда отмена предыдущей операции невозможна или не требуется, актуальность результата можно контролировать с помощью монотонно увеличивающейся версии:
Каждый запуск получает собственную версию, а
При нескольких асинхронных этапах проверку следует выполнять после каждой границы, после которой выполнение может продолжиться с неактуальными данными:
Проверка версии не отменяет уже выполняющуюся работу. Она определяет только то, сохраняет ли результат право влиять на текущее состояние:
🔥 Такой version token реализует семантику
📣 JS Ready | #практика
Порядок запуска асинхронных операций не определяет порядок их завершения. Если несколько вызовов обновляют одно состояние, более ранний запрос может завершиться позже и перезаписать актуальные данные:
let state;
async function loadUser(id) {
const user = await fetchUser(id);
state = user;
}
loadUser(1);
loadUser(2);
Если
fetchUser(1) завершится после fetchUser(2), итоговым значением state станет пользователь 1, хотя последним был запущен loadUser(2).Когда отмена предыдущей операции невозможна или не требуется, актуальность результата можно контролировать с помощью монотонно увеличивающейся версии:
let state;
let version = 0;
async function loadUser(id) {
const currentVersion = ++version;
const user = await fetchUser(id);
if (currentVersion !== version) {
return;
}
state = user;
}
Каждый запуск получает собственную версию, а
version хранит версию последнего запуска. Если после await значения различаются, результат уже устарел и не должен изменять состояние.При нескольких асинхронных этапах проверку следует выполнять после каждой границы, после которой выполнение может продолжиться с неактуальными данными:
async function loadUser(id) {
const currentVersion = ++version;
const user = await fetchUser(id);
if (currentVersion !== version) {
return;
}
const permissions = await fetchPermissions(user.id);
if (currentVersion !== version) {
return;
}
state = { user, permissions };
}Проверка версии не отменяет уже выполняющуюся работу. Она определяет только то, сохраняет ли результат право влиять на текущее состояние:
loadUser(1); // version = 1
loadUser(2); // version = 2
// version = 1 игнорируется
// version = 2 может обновить state
latest invocation wins: завершение асинхронной операции отделяется от актуальности её результата и предотвращает запись устаревших данных.Please open Telegram to view this post
VIEW IN TELEGRAM
🔥8❤7🤝4👍2