Разбираем способ объединять отмену из нескольких источников!
Ситуация: есть
Когда условий больше одного, код часто начинает разрастаться: вручную прокидываются отмены, добавляются таймауты, появляется лишняя синхронизация. Это работает, но плохо масштабируется и легко ломается.
Нативное решение —
Мы просто описываем правило: отмени, если сработает любой из сигналов. Теперь операция отменится либо вручную через
Использование с
Если
Разбираем кейс — последний запрос побеждает + таймаут:
Что это даёт: старые запросы отменяются и текущий не висит бесконечно. Важно: в этом примере при отмене
Комбинация с lifecycle:
Ты не завязываешься на конкретный источник отмены — просто собираешь сигналы.
Причина берётся от первого сработавшего сигнала:
если
API относительно новый. В Node:
🔥 Как только появляется больше одного условия отмены —
📣 JS Ready | #практика
Ситуация: есть
async операция, и её нужно отменять по разным причинам: пользователь ушёл со страницы / компонент размонтировался, истёк таймаут, пришёл новый запрос.Когда условий больше одного, код часто начинает разрастаться: вручную прокидываются отмены, добавляются таймауты, появляется лишняя синхронизация. Это работает, но плохо масштабируется и легко ломается.
Нативное решение —
AbortSignal.any():const controller = new AbortController();
const signal = AbortSignal.any([
controller.signal,
AbortSignal.timeout(3000)
]);
Мы просто описываем правило: отмени, если сработает любой из сигналов. Теперь операция отменится либо вручную через
controller.abort(), либо по таймауту.Использование с
fetch:try {
const res = await fetch("/api/data", { signal });
const data = await res.json();
} catch (err) {
if (signal.aborted) {
// отмена: manual abort, timeout или другой источник
return;
}
throw err;
}
Если
reject не обработан — будет unhandled rejection. Поведение: если вызвать controller.abort() раньше таймаута — сработает он, если не вызывать — операция завершится по таймауту. Комбинированный сигнал абортится один раз — по первому событию.Разбираем кейс — последний запрос побеждает + таймаут:
let controller;
async function load() {
controller?.abort();
controller = new AbortController();
const signal = AbortSignal.any([
controller.signal,
AbortSignal.timeout(5000)
]);
try {
const res = await fetch("/api", { signal });
return res.json();
} catch (err) {
if (signal.aborted) return;
throw err;
}
}
Что это даёт: старые запросы отменяются и текущий не висит бесконечно. Важно: в этом примере при отмене
load() вернёт undefined, потому что мы явно делаем return в catch. Это нужно учитывать в вызывающем коде.Комбинация с lifecycle:
function load(signalFromUI) {
const signal = AbortSignal.any([
signalFromUI,
AbortSignal.timeout(3000)
]);
return fetch("/api", { signal });
}
Ты не завязываешься на конкретный источник отмены — просто собираешь сигналы.
AbortSignal.any() создаёт новый сигнал со своим lifecycle.Причина берётся от первого сработавшего сигнала:
const controller = new AbortController();
const signal = AbortSignal.any([
controller.signal,
AbortSignal.timeout(3000)
]);
controller.abort("manual");
console.log(signal.reason); // "manual"
если
abort() без аргумента, то обычно будет AbortError, если сработал AbortSignal.timeout() — будет TimeoutError, если abort("manual") — reason будет "manual".API относительно новый. В Node:
AbortSignal.timeout() есть с Node 16.14.0 / 17.3.0; AbortSignal.any() есть с Node 18.17.0 / 20.3.0AbortSignal.any() упрощает код и убирает ручную синхронизацию.Please open Telegram to view this post
VIEW IN TELEGRAM
👍11❤7🔥7
This media is not supported in your browser
VIEW IN TELEGRAM
ESLint — это не просто подсветка ошибок, а инструмент анализа кода. Он находит потенциальные баги, антипаттерны и несоответствие правилам проекта, а также умеет автоматически исправлять часть проблем. Работает через конфиги и плагины (React, Next.js и др.), помогает держать код единообразным.
Please open Telegram to view this post
VIEW IN TELEGRAM
👍14❤6🤝6
Почему объекты не равны и как их сравнить без библиотек?
В JavaScript объекты сравниваются по ссылке, а не по содержимому.
Даже если структура полностью совпадает — это разные объекты.
Для простых данных можно использовать JSON.stringify.
Но важно понимать ограничения:
Также не работает с undefined, функциями, Symbol и BigInt.
🔥 Приём для тестов, простых кешей и сравнений JSON-данных.
📣 JS Ready | #совет
В JavaScript объекты сравниваются по ссылке, а не по содержимому.
{ x: 1 } === { x: 1 } // falseДаже если структура полностью совпадает — это разные объекты.
Для простых данных можно использовать JSON.stringify.
JSON.stringify(a) === JSON.stringify(b) // true
Но важно понимать ограничения:
JSON.stringify({ a: 1, b: 2 }) === JSON.stringify({ b: 2, a: 1 })
// false (порядок ключей важен)Также не работает с undefined, функциями, Symbol и BigInt.
Please open Telegram to view this post
VIEW IN TELEGRAM
👍11❤8🔥7
Например, TTFB показывает, как быстро сервер начинает отвечать, а FCP помогает понять, когда пользователь впервые видит контент на экране.
На картинке — 9 основных метрик производительности сайта, которые полезно держать под рукой каждому разработчику.
Сохрани, чтобы не потерять!
Please open Telegram to view this post
VIEW IN TELEGRAM
❤9👍6🤝5
Proxy — перехватываем изменения объекта!
Proxy — одна из возможностей JS, про которую многие знают только поверхностно. На практике он позволяет перехватывать чтение, запись, удаление и даже вызовы функций, превращая обычный объект в управляемую структуру данных.
Создадим объект и обернём его в Proxy:
Теперь любое изменение объекта можно контролировать централизованно.
Перехватываем запись значений:
Пробуем изменить объект:
Даже новые свойства автоматически проходят через обработчик.
Через Proxy удобно валидировать данные до записи:
Теперь объект сам защищает свои данные от невалидных значений.
Завершаем логику и тестируем:
Такой подход часто используют в системах реактивности, конфиг-движках, ORM и инструментах разработки, где важно централизованно контролировать изменения данных.
🔥 Proxy позволяет буквально вклиниться между кодом и объектом, превращая обычные структуры данных в управляемые и расширяемые механизмы с собственной логикой поведения.
📣 JS Ready | #практика
Proxy — одна из возможностей JS, про которую многие знают только поверхностно. На практике он позволяет перехватывать чтение, запись, удаление и даже вызовы функций, превращая обычный объект в управляемую структуру данных.
Создадим объект и обернём его в Proxy:
const user = { name: "Alex" };
const proxy = new Proxy(user, {
set(target, key, value) {Теперь любое изменение объекта можно контролировать централизованно.
Перехватываем запись значений:
console.log(` ${key} = ${value}`);
target[key] = value;
return true; }}
);set вызывается каждый раз при изменении свойства — это основа реактивности во многих frontend-фреймворках.Пробуем изменить объект:
proxy.name = "John";
proxy.age = 30;
// name = John
// age = 30
Даже новые свойства автоматически проходят через обработчик.
Через Proxy удобно валидировать данные до записи:
const settings = new Proxy({}, {
set(obj, key, value) {
if (key === "port" && value > 65535)
throw Error("Некорректный порт");Теперь объект сам защищает свои данные от невалидных значений.
Завершаем логику и тестируем:
obj[key] = value;
return true;
}
});
settings.port = 3000;
Такой подход часто используют в системах реактивности, конфиг-движках, ORM и инструментах разработки, где важно централизованно контролировать изменения данных.
Please open Telegram to view this post
VIEW IN TELEGRAM
🔥13❤4👍4
This media is not supported in your browser
VIEW IN TELEGRAM
На сайте собраны не только шпаргалки, но и полноценные статьи с разбором разных тем: работа с массивами и объектами, функции, замыкания, методы JS и практические нюансы разработки. Материал подаётся с примерами кода и краткими пояснениями.
Please open Telegram to view this post
VIEW IN TELEGRAM
❤15👍7🤝6
Разбираем как не словить утечку памяти на обработчиках событий!
Одна из частых причин утечек — обработчик повесили, а снять забыли. Особенно это неприятно, когда событие висит не на локальном элементе, а на чём-то долгоживущем:
Компонента уже нет, а
Правильнее сразу закладывать
Главное — снимать нужно ту же самую функцию.
Вот так не сработает:
Это две разные функции, даже если выглядят одинаково.
Нормальный вариант:
Ещё момент —
Так обработчик не снимется, потому что
Когда обработчиков несколько, удобно использовать
А если обработчик нужен только один раз:
После первого вызова он удалится сам. Важный нюанс: удалённый DOM сам по себе ещё не утечка. Если на него нет внешних ссылок — GC его заберёт.
🔥 Проблемы начинаются, когда: обработчик висит на
📣 JS Ready | #практика
Одна из частых причин утечек — обработчик повесили, а снять забыли. Особенно это неприятно, когда событие висит не на локальном элементе, а на чём-то долгоживущем:
window, document, глобальный event bus и т.п.
function mount() {
window.addEventListener('resize', onResize);
}
function unmount() {
// забыли снять обработчик
}
Компонента уже нет, а
onResize всё ещё вызывается. Плюс callback может держать в замыкании старое состояние, DOM-ноды, данные — и GC не сможет это нормально прибрать.Правильнее сразу закладывать
cleanup:
function mount() {
window.addEventListener('resize', onResize);
}
function unmount() {
window.removeEventListener('resize', onResize);
}
Главное — снимать нужно ту же самую функцию.
Вот так не сработает:
el.addEventListener('click', () => doSomething());
el.removeEventListener('click', () => doSomething());
Это две разные функции, даже если выглядят одинаково.
Нормальный вариант:
function handler() {
doSomething();
}
el.addEventListener('click', handler);
el.removeEventListener('click', handler);
Ещё момент —
capture.
el.addEventListener('click', handler, true);
el.removeEventListener('click', handler, false);
Так обработчик не снимется, потому что
capture отличается. Для удаления браузер сравнивает type, listener и capture.passive, once, signal в этом сравнении не участвуют.Когда обработчиков несколько, удобно использовать
AbortController:
const controller = new AbortController();
window.addEventListener('resize', onResize, {
signal: controller.signal
});
document.addEventListener('visibilitychange', onVisible, {
signal: controller.signal
});
// cleanup
controller.abort();
abort() снимет все обработчики, которые были добавлены с этим signal.А если обработчик нужен только один раз:
button.addEventListener('click', handler, { once: true });
После первого вызова он удалится сам. Важный нюанс: удалённый DOM сам по себе ещё не утечка. Если на него нет внешних ссылок — GC его заберёт.
window / document; callback держит старое состояние в замыкании; DOM-нода или данные лежат в массиве, store или кэше.Please open Telegram to view this post
VIEW IN TELEGRAM
👍11❤8🔥6
This media is not supported in your browser
VIEW IN TELEGRAM
Colorize — подсвечивает все цветовые значения прямо в редакторе: HEX, RGB, HSL, CSS-переменные и другие форматы. Вместо набора символов ты сразу видишь реальный цвет рядом с кодом. Удобно при работе с UI, темами и большими CSS/SCSS-файлами, где важно быстро ориентироваться в палитре проекта.
Please open Telegram to view this post
VIEW IN TELEGRAM
❤10👍10🤝7
Please open Telegram to view this post
VIEW IN TELEGRAM
Please open Telegram to view this post
VIEW IN TELEGRAM
🔥11❤6👍6
Например, REST отлично подходит для простых CRUD API, GraphQL позволяет получать только нужные данные одним запросом, а gRPC обеспечивает максимально быстрое взаимодействие между микросервисами через HTTP/2 и Protobuf.
На картинке — основные отличия REST, GraphQL и gRPC: принципы работы, структура запросов, особенности передачи данных и сценарии использования.
Сохрани, чтобы не потерять!
Please open Telegram to view this post
VIEW IN TELEGRAM
🔥11👍6🤝4❤1
Почему await внутри forEach не работает как ожидается?
Многие думают, что
Из-за этого легко получить гонки, неожиданный порядок выполнения и баги.
Правильный способ —
Теперь каждая операция действительно ждёт предыдущую.
Если нужна параллельность — используйте
🔥 Это одна из самых частых async-ловушек в JavaScript. Особенно полезно в API, очередях задач, миграциях и любом коде, где важен контроль выполнения.
📣 JS Ready | #совет
Многие думают, что
await внутри forEach остановит выполнение цикла. Но forEach не работает с Promise и не ждёт завершения callback.console.log('done'); // выполнится раньше запросовИз-за этого легко получить гонки, неожиданный порядок выполнения и баги.
Правильный способ —
for...of.for (const id of ids) {
await fetch(`/user/${id}`);
}Теперь каждая операция действительно ждёт предыдущую.
Если нужна параллельность — используйте
Promise.all.await Promise.all(
ids.map(id => fetch(`/user/${id}`))
);
Please open Telegram to view this post
VIEW IN TELEGRAM
🔥15👍6🤝5❤1
Please open Telegram to view this post
VIEW IN TELEGRAM
👍17❤6🔥5
Например, Session Cookie хранит session ID на клиенте, а JWT позволяет серверу проверять пользователя без хранения сессии.
На картинке — основные подходы: WWW-Authenticate, Session Cookie, JWT, Token-based auth, SSO и OAuth 2.0 grant flows.
Сохрани, чтобы не потерять!
Please open Telegram to view this post
VIEW IN TELEGRAM
👍11❤5🔥5
Как искать с конца массива без reverse и лишних копий?
Когда нужен последний подходящий элемент, часто сначала разворачивают массив или пишут цикл с конца:
В JavaScript для этого есть нативный метод
Он проходит массив с конца и сразу возвращает первый подходящий элемент.
Ничего не копирует и не мутирует:
Если нужен не сам элемент, а его позиция, рядом есть
🔥 Полезно для логов, истории действий, последних ошибок, последнего валидного значения и вообще любой логики, где искать нужно именно с конца.
📣 JS Ready | #совет
Когда нужен последний подходящий элемент, часто сначала разворачивают массив или пишут цикл с конца:
const lastError = [...logs].reverse().find(x => !x.ok);
В JavaScript для этого есть нативный метод
findLast():const lastError = logs.findLast(x => !x.ok);
Он проходит массив с конца и сразу возвращает первый подходящий элемент.
Ничего не копирует и не мутирует:
const lastBig = [10, 20, 30, 40].findLast(x => x > 15);
// 40
Если нужен не сам элемент, а его позиция, рядом есть
findLastIndex():const idx = logs.findLastIndex(x => !x.ok);
// 2
Please open Telegram to view this post
VIEW IN TELEGRAM
👍13🤝6🔥5❤1
This media is not supported in your browser
VIEW IN TELEGRAM
env-cmd-file-syntax — добавляет подсветку и поддержку синтаксиса для файлов env-cmd, помогая удобнее работать с переменными окружения. Расширение делает конфиги читаемее, упрощает навигацию и помогает быстрее замечать ошибки в названиях и структуре переменных.
Please open Telegram to view this post
VIEW IN TELEGRAM
❤10👍7🤝5🔥1
String.prototype.matchAll — когда нужно работать со всеми совпадениями и группами!
Обычно для поиска по строке используют
Базовый пример:
На выходе — последовательность результатов, аналогичных
Как ведёт себя
При глобальном поиске (g)
Извлечение структурированных данных:
Работа с индексами
Каждое совпадение содержит позицию (
Важно: нужен флаг g
Если передать RegExp без g, будет ошибка. Корректно:
Также можно передать строку — она будет неявно преобразована в RegExp с флагом g:
Именованные группы
Именованные группы повышают читаемость, особенно при большом количестве групп.
Отличие от
Подход рабочий, но есть нюансы: мутируется
🔥
📣 JS Ready | #практика
Обычно для поиска по строке используют
match или exec, но у них есть ограничения: либо приходится писать цикл с lastIndex, либо поведение с группами зависит от режима работы.matchAll решает это аккуратно — возвращает итератор по всем совпадениям с доступом к группам, без ручного управления состоянием.Базовый пример:
const str = "id=1 id=2 id=3";
const matches = str.matchAll(/id=(\d+)/g);
for (const m of matches) {
console.log(m[1]);
}
// 1 2 3
На выходе — последовательность результатов, аналогичных
exec: это массивы с доступом к группам, индексу и исходной строке. Разница в том, что не нужно вручную крутить цикл и следить за lastIndex.Как ведёт себя
match, здесь часто возникает путаница — match работает по-разному в зависимости от флага g:"id=1".match(/id=(\d+)/);
// ["id=1", "1", ...]
"id=1 id=2".match(/id=(\d+)/g);
// ["id=1", "id=2"]
При глобальном поиске (g)
match возвращает только полные совпадения — без capture groups. Это как раз типичный кейс, где matchAll удобнее.Извлечение структурированных данных:
const str = "x:10 y:20 z:30";
const result = [...str.matchAll(/(\w):(\d+)/g)]
.map(([, key, val]) => [key, Number(val)]);
console.log(Object.fromEntries(result));
// { x: 10, y: 20, z: 30 }
matchAll позволяет сразу получить пары ключ–значение без ручного управления состоянием регулярки.Работа с индексами
const str = "test1 test2";
for (const m of str.matchAll(/\w+\d/g)) {
console.log(m[0], m.index);
}
// test1 0
// test2 6
Каждое совпадение содержит позицию (
index), что удобно для парсинга, подсветки, токенизации.Важно: нужен флаг g
"abc".matchAll(/a/);
// TypeError
Если передать RegExp без g, будет ошибка. Корректно:
[..."abc".matchAll(/a/g)];
Также можно передать строку — она будет неявно преобразована в RegExp с флагом g:
[..."abcabc".matchAll("a")];
Именованные группы
const str = "2026-04-19";
for (const m of str.matchAll(
/(?<year>\d{4})-(?<month>\d{2})-(?<day>\d{2})/g
)) {
console.log(m.groups.year);
}
Именованные группы повышают читаемость, особенно при большом количестве групп.
Отличие от
exec:const str = "id=1 id=2 id=3";
const re = /id=(\d+)/g;
let m;
while ((m = re.exec(str))) {
console.log(m[1]);
}
Подход рабочий, но есть нюансы: мутируется
lastIndex, поведение зависит от повторного использования RegExp, легко допустить скрытую ошибку.matchAll решает это: он использует внутренний клон регулярки, поэтому не мутирует исходный RegExp и не требует ручного контроля состояния.Please open Telegram to view this post
VIEW IN TELEGRAM
❤10👍6🤝4
This media is not supported in your browser
VIEW IN TELEGRAM
На сайте собраны статьи и интерактивные туториалы по JavaScript, React, CSS-анимациям, Flexbox, Grid и архитектуре интерфейсов. Автор объясняет сложные концепции простым языком и показывает не только как, но и почему это работает.
Please open Telegram to view this post
VIEW IN TELEGRAM
❤12👍7🤝5