es
Feedback
Настоящий JavaScript

Настоящий JavaScript

Ir al canal en Telegram

Тот самый канал по JavaScript. Личный блог автора - @just_genych По вопросам рекламы или разработки: @g_abashkin

Mostrar más
6 252
Suscriptores
-2724 horas
-597 días
+2530 días
Atraer Suscriptores
junio '26
junio '26
+103
en 0 canales
mayo '26
+384
en 0 canales
Get PRO
abril '26
+83
en 0 canales
Get PRO
marzo '26
+71
en 0 canales
Get PRO
febrero '26
+23
en 0 canales
Get PRO
enero '26
+18
en 0 canales
Get PRO
diciembre '25
+13
en 0 canales
Get PRO
noviembre '25
+24
en 0 canales
Get PRO
octubre '25
+22
en 0 canales
Get PRO
septiembre '25
+21
en 0 canales
Get PRO
agosto '25
+20
en 0 canales
Get PRO
julio '25
+38
en 0 canales
Get PRO
junio '25
+52
en 0 canales
Get PRO
mayo '25
+121
en 0 canales
Get PRO
abril '25
+88
en 0 canales
Get PRO
marzo '25
+363
en 20 canales
Get PRO
febrero '25
+30
en 0 canales
Get PRO
enero '25
+38
en 0 canales
Get PRO
diciembre '24
+41
en 0 canales
Get PRO
noviembre '24
+64
en 2 canales
Get PRO
octubre '24
+70
en 1 canales
Get PRO
septiembre '24
+84
en 2 canales
Get PRO
agosto '24
+70
en 0 canales
Get PRO
julio '24
+115
en 0 canales
Get PRO
junio '24
+89
en 4 canales
Get PRO
mayo '24
+101
en 0 canales
Get PRO
abril '24
+121
en 0 canales
Get PRO
marzo '24
+132
en 0 canales
Get PRO
febrero '24
+101
en 0 canales
Get PRO
enero '24
+86
en 0 canales
Get PRO
diciembre '23
+90
en 0 canales
Get PRO
noviembre '23
+16
en 0 canales
Get PRO
octubre '23
+15
en 0 canales
Get PRO
septiembre '23
+16
en 0 canales
Get PRO
agosto '23
+24
en 0 canales
Get PRO
julio '23
+35
en 0 canales
Get PRO
junio '23
+20
en 0 canales
Get PRO
mayo '23
+15
en 0 canales
Get PRO
abril '23
+264
en 0 canales
Get PRO
marzo '23
+17
en 0 canales
Get PRO
febrero '23
+207
en 0 canales
Get PRO
enero '23
+40
en 0 canales
Get PRO
diciembre '22
+478
en 0 canales
Get PRO
noviembre '22
+1 627
en 0 canales
Get PRO
octubre '22
+45
en 0 canales
Get PRO
septiembre '22
+451
en 0 canales
Get PRO
agosto '22
+459
en 0 canales
Get PRO
julio '22
+1 991
en 0 canales
Get PRO
junio '22
+1 567
en 0 canales
Get PRO
mayo '22
+1 769
en 0 canales
Get PRO
abril '22
+97
en 0 canales
Get PRO
marzo '22
+2 701
en 0 canales
Fecha
Crecimiento de Suscriptores
Menciones
Canales
20 junio0
19 junio0
18 junio0
17 junio0
16 junio+1
15 junio+1
14 junio+2
13 junio+2
12 junio+11
11 junio+39
10 junio+24
09 junio+17
08 junio0
07 junio0
06 junio0
05 junio+2
04 junio0
03 junio0
02 junio+2
01 junio+2
Publicaciones del Canal
Регулярки в JS: когда pattern matching сжигает CPU Регулярные выражения мощны, но неаккуратное использование в production — будь то валидация форм в SPA, парсинг данных на Node.js бэкенде или обработка логов — может привести к зависанию сервера. Самая частая ошибка — catastrophic backtracking, когда движок перебирает экспоненциальное количество комбинаций из-за вложенных квантификаторов. Как возникает катастрофический возврат Классический пример — валидация email:
/^([a-z]+)+@domain\.com$/
Пускаем строку aaaaaaaaaaaaaaaaaaaa!@domain.com. Движок не находит совпадение после ! и начинает перебирать варианты группировки внутри (a+)+: 20 символов, потом 19+1, 18+2... Получается ~2^20 путей. На 30 символах время выполнения растёт с миллисекунд до минут. Необходимы три условия: * Вложенные квантификаторы — + внутри + или * внутри *. * Отсутствие фиксированного символа, прерывающего перебор. * Неудачное совпадение в конце — движок возвращается к предыдущим вариантам. Типичные паттерны-ловушки Часто встречаются в коде парсинга HTML или валидации чисел:
/<(.+)>.+<\/\1>/  — разбор тегов
/^(-?\d+(\.\d+)?)+$/  — валидация чисел
Оба дают экспоненту на длинных строках с ошибкой. В боевом API-клиенте или SDK такое приведёт к зависанию при обработке вредоносного ввода. Как защититься Первый способ — эмулировать atomic groups через lookahead:
/(?=(a+))\1/  — захват без возврата
Второй — конкретизировать границы. Вместо .* используйте точные классы. Для email:
/^([a-z\d._%+-]+@[a-z\d.-]+\.[a-z]{2,})$/i
Третий — timeout. На Node.js используйте библиотеку re2 — она гарантирует O(n) и не залипает. Или оборачивайте регулярку в Web Worker с таймером. Цифры и инструменты На строке из 30 символов простая регулярка отрабатывает ~0.01ms, катастрофическая — >5000ms, рост экспоненциальный. Для дебага используйте regex101.com — там видно количество шагов. В Node.js можно увеличить --stack-size, но проще переписать паттерн. Вывод: Избегайте вложенных квантификаторов на одинаковых группах символов — это предотвратит catastrophic backtracking и спасёт CPU вашего production.

2
Gzip и Brotli — зло для стриминга. Почему продвинутые переходят на Zstd Если ты используешь compression() из Express для всех
Gzip и Brotli — зло для стриминга. Почему продвинутые переходят на Zstd Если ты используешь compression() из Express для всех API, ты ломаешь стриминг. Gzip и Brotli ждут полный буфер — res.write() по чанкам не сработает, сжатие начнётся только после res.end(). Это убивает SSE, пагинацию с курсорами и загрузку с прогрессом в production. Частая ошибка: разработчики ставят middleware сжатия глобально, не осознавая, что TTFB растет до момента полной отдачи ответа. Компрессия против буферизации app.use(compression()); // ждёт весь ответ app.get('/stream', (req, res) => { for (let i = 0; i < 1000; i++) res.write(chunk ${i}\n); // не сжимается res.end(); // только тут сжатие }); Gzip и Brotli требуют всего ответа для построения словаря. Для потоков данных — например, курсорной пагинации — это неприемлемо. Клиент не получит ни единого байта, пока сервер не завершит поток. Zstd как решение для стриминга Zstandard (RFC 8478) спроектирован для потокового сжатия: не накапливает буфер, сжимает на лету. Уровень 1 даёт ~10-20 ГБ/с, что достаточно для большинства серверов. Пример настройки через node-zstd: import { Transform } from 'stream'; import { compress } from 'node-zstd'; const zstdStream = new Transform({ transform(chunk, _, callback) { compress(chunk, 3) .then(c => callback(null, c)) .catch(callback); } }); app.get('/api/items', (req, res) => { res.setHeader('Content-Encoding', 'zstd'); getChunkedDataCursor().pipe(zstdStream).pipe(res); }); Ключевой trade-off: не ставь уровень 22 — он жрёт память как Gzip -9. Для пагинации достаточно level 1-3 с window size 15 (32KB). Для статики можно 22 (4MB), но смотри по RPS. Бенчмарки и практические советы * Предкомпилированные словари для повторяющихся структур (DTO) дают до 40% ускорение на бенчмарках. * На production: | Алгоритм | TTFB (мс) | Throughput (MB/s) | |----------|-----------|-------------------| | Gzip -9 | 450 | 120 | | Brotli -11| 890 | 85 | | Zstd -3 | 78 | 980 | Zstd даёт почти размер Gzip, но в 10 раз быстрее. Без блокировок на whole response — идеально для стриминга и пагинации. Ошибка: не все браузеры поддерживают Zstd. Всегда проверяй Accept-Encoding и делай fallback на Brotli для клиентов. Для backend-to-backend коммуникации Zstd однозначно, как делает Netflix. Вывод: Gzip и Brotli оставь для статики с малым числом запросов; для стриминга, пагинации и SSE используй Zstd с Transform Stream — это даёт на порядок лучший TTFB и пропускную способность без компромиссов по сжатию.
153
3
Слабые ссылки и финализаторы: когда Rust не нужен, но GC бессилен Управление памятью в JS — это иллюзия, пока не начнутся пад
Слабые ссылки и финализаторы: когда Rust не нужен, но GC бессилен Управление памятью в JS — это иллюзия, пока не начнутся падения RSS в production. WeakRef и FinalizationRegistry — не инструменты для ежедневного кода, а спасение в сценариях, где классические паттерны утечек уже вылечены, а память продолжает расти. В production это актуально для long-running SPA, тяжёлых кэшей на Node.js и интеграций с нативными ресурсами вроде Canvas. WeakRef: кэш, который убивает сам себя Когда Map с картинками или JSON-ответами держит сильные ссылки — это утечка, если объект больше не нужен. WeakRef позволяет хранить ссылку, которую GC может забрать при нехватке памяти. Используйте для кэширования тяжёлых данных или слабых подписок в Observer-паттерне. class SmartCache { private data = new Map<string, WeakRef<object>>(); set(key: string, value: object) { this.data.set(key, new WeakRef(value)); } get(key: string): object | undefined { return this.data.get(key)?.deref(); } } * Минус: .deref() может вернуть undefined в любой момент — обрабатывайте это явно. FinalizationRegistry: cleanup для внешних ресурсов Колбэк вызывается после сборки объекта. Нужен для освобождения WebSocket-соединений, файловых дескрипторов или памяти GPU. Не используйте для закрытия транзакций — колбэк может прийти с задержкой или не прийти до завершения процесса. const registry = new FinalizationRegistry((id: string) => { console.log(Cleanup: ${id}); }); class Heavy { constructor(public id: string) { registry.register(this, id); } } * Типичная ошибка: делать в колбэке тяжёлую работу — V8 может зависнуть event loop. Где без них реально не обойтись * Long-running SPA с вкладками, которые не пересоздаются. * Canvas/WebGL: ручное управление памятью — часть архитектуры. * Node.js: кэши изображений или HLS-фрагментов в высоконагруженных сервисах. Что ломается в production * V8 реализует WeakRef с задержками — мгновенной очистки не ждите. * FinalizationRegistry может вызвать колбэк не в том порядке. * Это не замена грамотному управлению ссылками, а дополнительный слой для узких мест. Как избежать боли Напишите модуль-обёртку: внутри WeakRef и Registry, снаружи чистый API с fallback на пересоздание или ошибку. Разработчику не нужно знать о слабых ссылках. Тестируйте с помощью --expose-gc и следите за кучей в проде. Вывод: WeakRef — для кэшей и подписок, FinalizationRegistry — для внешних ресурсов, но оба требуют явной обработки исчезновения объекта и не отменяют необходимость профилирования памяти.
149
4
Утечки памяти в production: как отлавливать то, что не видно в логах Каждый раз, когда слышу "у нас утечка памяти", первая мы
Утечки памяти в production: как отлавливать то, что не видно в логах Каждый раз, когда слышу "у нас утечка памяти", первая мысль - кто-то забыл удалить слушатель событий или не очистил setInterval. В production все сложнее: утечки копятся неделями, проявляются при определенной нагрузке, и локально их не воспроизвести. Вот три рабочих инструмента. Heap snapshots: как читать между строк Снимаешь снапшот в Chrome DevTools (Memory -> Take heap snapshot). Сравниваешь два: до и после выполнения подозрительного сценария. Ищешь объекты, которые: - Не должны быть в памяти, но есть (старые DOM-ноды с замыканиями) - Имеют неожиданно много экземпляров (10 000 объектов класса Widget) Если видишь Closure с огромным retained size - это замыкание, которое держит ссылку на внешние данные. В production такая утечка часто возникает в SSR, когда обработчик запроса захватывает ссылку на глобальный кэш. Memory tab: интерактивный детектив Переходишь на вкладку Memory, выбираешь "Allocation instrumentation on timeline". Записываешь профиль при активной работе пользователя. Красные пики в столбчатой диаграмме - места, где память выделяется, но не освобождается. Клик по пику покажет стек вызовов в момент аллокации. Паттерн, который ловил так: React-компонент, при каждом ререндере создавал новый объект-конфигурацию и передавал в children - GC не успевал чистить, через час работы страница весила 200+ МБ. Автоматизированные паттерны детекции Ручной анализ хорошо, но в production нужно автоматическое обнаружение. Два подхода. Performance Observer с проверкой usedJSHeapSize: new PerformanceObserver((list) => { const entries = list.getEntries(); if (performance.memory?.usedJSHeapSize > 200_000_000) { console.warn('Memory warning', performance.memory); } }).observe({ type: 'resource', buffered: true }); Только в Chrome с флагом enable-experimental-web-platform-features. Типичная ошибка - использовать это без fallback в Safari или Firefox. Кастомный мониторинг: setInterval(() => { const { usedJSHeapSize, totalJSHeapSize } = performance.memory; const usagePercent = (usedJSHeapSize / totalJSHeapSize) * 100; if (usagePercent > 80) { sendMetric('memory_leak_risk', usedJSHeapSize); } }, 60000); Не используй для продакшена бездумно - performance.memory есть не везде. Оборачивай в try/catch и делай fallback на navigator.userAgent. Утечки в production - это не баги, а архитектурные проблемы. Если после каждого перехода по роуту память растет на 5-10% и не падает - ищи подписки в эффектах, которые не отписываются при размонтировании. Или глобальные кэши, которые никогда не чистятся. Вывод: Надежная детекция утечек требует сочетания автоматических инструментов и ручного анализа heap snapshots, а не надежды на GC или банальные логи.
192
5
🔴 Собеседование на Middle Frontend-разработчика начнётся уже через 2 часа. Переходи в бот за ссылкой: @shortcut_front_bot
218
6
Temporal в JavaScript: даты без production-боли от Date Date смешивает слишком много понятий: момент времени, локальное предс
Temporal в JavaScript: даты без production-боли от Date Date смешивает слишком много понятий: момент времени, локальное представление, таймзону окружения, парсинг строк и мутабельность. В итоге баги часто появляются не в unit-тестах, а в production: на DST-переходах, у пользователей из другой таймзоны или при переносе кода между Node.js и браузером. Temporal решает это за счёт явного разделения сущностей: • Temporal.Instant — точный момент времени, удобно хранить в UTC • Temporal.ZonedDateTime — дата/время + IANA timezone • Temporal.PlainDate — календарная дата без времени и таймзоны • Temporal.PlainTime — время без даты • Temporal.Duration — длительность • Temporal.PlainDateTime — локальные дата и время без привязки к зоне Главная идея: не притворяться, что «дата» — это одна универсальная сущность. Пример с DST: import { Temporal } from '@js-temporal/polyfill'; const meeting = Temporal.ZonedDateTime.from( '2024-03-30T12:00:00+01:00[Europe/Berlin]' ); console.log(meeting.add({ days: 1 }).toString()); // 2024-03-31T12:00:00+02:00[Europe/Berlin] console.log(meeting.add({ hours: 24 }).toString()); // 2024-03-31T13:00:00+02:00[Europe/Berlin] Это не одно и то же. add({ days: 1 }) означает «завтра в это же локальное время». add({ hours: 24 }) означает «через 24 реальных часа». На переходе на летнее время день может быть не 24 часа. Temporal делает это различие явным, а Date обычно прячет проблему до момента, когда пользователи начинают жаловаться. Ещё один важный кейс — несуществующее локальное время: try { Temporal.ZonedDateTime.from( '2024-03-31T02:30[Europe/Berlin]', { disambiguation: 'reject' } ); } catch { console.log('Такого локального времени нет из-за DST'); } В ночь перехода на летнее время 02:30 в Berlin может просто не существовать. В Date такие ситуации часто «нормализуются» молча. В Temporal можно явно выбрать стратегию: reject, earlier, later, compatible. Практические правила для production: 1. Храните точный момент как Instant Например, событие уже произошло или платёж создан. 2. Храните пользовательскую таймзону отдельно Не GMT+3, а IANA zone: Europe/Berlin, Asia/Tbilisi, America/New_York. 3. Для бизнес-правил используйте календарные типы День рождения — это PlainDate, а не Date в полночь UTC. «Каждый день в 09:00 по Москве» — это локальное время + timezone, а не setInterval(24h). 4. Не заменяйте календарные операции миллисекундами + 86400000 — это не всегда «завтра». На DST это может быть 23 или 25 локальных часов. 5. Делайте неоднозначность явной Если локальное время может не существовать или повторяться, выбирайте поведение явно через disambiguation. Temporal особенно полезен там, где цена ошибки высокая: расписания, биллинг, бронирования, напоминания, cron-like задачи, SLA, отчёты по локальным дням. Важно: поддержка Temporal зависит от runtime. Для production сейчас обычно используют polyfill @js-temporal/polyfill и постепенно готовятся к нативной поддержке. Коротко: Date хорош для простых timestamp-операций, но плохо моделирует реальные бизнес-даты. Temporal заставляет выбрать правильную модель времени — и именно поэтому снижает количество багов на таймзонах и DST.
207
7
Iterator Helpers в JavaScript: ленивые пайплайны без промежуточных массивов и ловушка одноразовых итераторов Iterator Helpers
Iterator Helpers в JavaScript: ленивые пайплайны без промежуточных массивов и ловушка одноразовых итераторов Iterator Helpers добавляют к итераторам map, filter, take, reduce, toArray и другие методы в стиле массивов. Это полезно в API clients, Node.js-сервисах, SSR и build tooling, но частая ошибка - воспринимать iterator pipeline как переиспользуемую коллекцию. Ленивость вместо временных массивов const emails = users .values() .filter(user => user.active) .map(user => user.email) .take(10) .toArray(); Здесь не создаются массивы после filter и map. Элементы проходят цепочку по одному: user -> filter -> map -> take. Это выигрывает, когда данных много, нужен только префикс результата или источник потенциально бесконечный: function* numbers() { let i = 0; while (true) yield i++; } const result = numbers() .filter(n => n % 2 === 0) .map(n => n * n) .take(5) .toArray(); Подводный камень: итератор одноразовый const iterator = [1, 2, 3] .values() .map(x => x * 2); iterator.toArray(); // [2, 4, 6] iterator.toArray(); // [] Это не баг, а модель курсора. Если часть данных уже прочитана через next(), пайплайн продолжит с текущей позиции. Практический совет Не передавайте iterator pipeline как “коллекцию” между модулями: const activeUsers = users .values() .filter(user => user.active); sendEmails(activeUsers); renderUsers(activeUsers); // может быть пусто Если результат нужен несколько раз, материализуйте его: const activeUsers = users .values() .filter(user => user.active) .toArray(); Или отдавайте фабрику нового итератора: const activeUsers = () => users.values().filter(user => user.active); Терминальные операции запускают проход map, filter, take ленивые. toArray, reduce, forEach, some, every, find реально потребляют итератор. Вывод: Iterator Helpers хороши для читаемых и экономных пайплайнов, но надежная архитектура требует явно решать, где итератор одноразовый, а где нужна материализованная коллекция.
221
8
Explicit Resource Management в JS/TS: using, await using и безопасный cleanup в async-коде без try/finally в каждом месте В J
Explicit Resource Management в JS/TS: using, await using и безопасный cleanup в async-коде без try/finally в каждом месте В JS/TS появляется детерминированное освобождение ресурсов: не когда GC соберет объект, а при выходе из scope. В production это важно для Node.js-сервисов, SSR, SDK, API clients и shared libraries, где часто забывают вызвать close, release, rollback или unsubscribe. Идея Ресурс реализует протокол, а язык вызывает cleanup сам: * using - синхронный [Symbol.dispose]() * await using - асинхронный [Symbol.asyncDispose]() await using дожидается cleanup, поэтому подходит для DB connection, Redis lock, stream, socket, transaction. Production-паттерн: транзакция class Tx { committed = false; async commit() { await db.commit(); this.committed = true; } async [Symbol.asyncDispose]() { if (!this.committed) await db.rollback(); } } async function createOrder(input: OrderInput) { await using tx = await db.beginTransaction(); const order = await tx.orders.create(input); await tx.audit.log("order_created", order.id); await tx.commit(); return order; } Если audit.log выбросит ошибку, транзакция будет откатана без ручного try/finally в каждом use case. Важные детали * dispose вызывается в обратном порядке объявления * cleanup выполняется при return и throw * ошибки из основного кода и dispose не теряются, используется SuppressedError * это не замена GC: GC про память, using - про внешние ресурсы Практический совет Для библиотечного API проектируйте не контракт "не забудь вызвать .close()", а явный протокол [Symbol.dispose] / [Symbol.asyncDispose]. В TypeScript синтаксис поддерживается с TS 5.2. Предупреждение: если cleanup возвращает Promise, обычный using не подходит - используйте await using. Вывод: using и await using делают жизненный цикл ресурсов явным, снижают риск утечек и улучшают надежность async-кода без разрастания try/finally.
238
9
🔍Тестовое собеседование с руководителем Frontend-разработки в этот четверг 18 июня(в четверг!) в 19:00 по мск приходи онлайн
🔍Тестовое собеседование с руководителем Frontend-разработки в этот четверг 18 июня(в четверг!) в 19:00 по мск приходи онлайн на открытое собеседование, чтобы посмотреть на настоящее интервью на Middle Frontend-разработчика. Как это будет: 📂 Виталий Черков, руководитель группы Frontend разработки с опытом 8+ лет, будет задавать реальные вопросы и задачи разработчику-добровольцу 📂 Виталий будет комментировать каждый ответ респондента, чтобы дать понять, чего от вас ожидает собеседующий на интервью 📂 В конце можно будет задать любой вопрос Виталию Это бесплатно. Эфир проходит в рамках менторской программы от ШОРТКАТ для Frontend-разработчиков, которые хотят повысить свой грейд, ЗП и прокачать скиллы. Переходи в нашего бота, чтобы получить ссылку на эфир → @shortcut_front_bot Реклама. О рекламодателе.
230
10
Web Locks API в браузере: cross-tab mutex для refresh token, миграций и защиты от гонок Когда одна сессия открыта в нескольки
Web Locks API в браузере: cross-tab mutex для refresh token, миграций и защиты от гонок Когда одна сессия открыта в нескольких вкладках, frontend, SPA, SSR-клиенты и SDK начинают делить auth state, storage и кэш. Частая ошибка - защищать refresh token только in-memory single-flight: между вкладками он не работает. Refresh token как критическая секция Если 5 вкладок одновременно получили 401, они могут отправить несколько refresh-запросов одним token, получить invalid_grant и перетереть свежие токены старыми. Web Locks API дает mutex на один origin: type Tokens = { accessToken: string; refreshToken: string; }; const LOCK = 'auth:refresh-token'; async function ensureAccessToken(): Promise<string> { return navigator.locks.request(LOCK, async () => { const latest: Tokens = await loadTokens(); if (!isExpiring(latest)) { return latest.accessToken; } const next = await refreshTokens(latest.refreshToken); await saveTokens(next); return next.accessToken; }); } Важный паттерн После входа в lock нужно перечитать storage. Пока вкладка ждала, другая уже могла обновить токены. Практический совет: внутри критической секции всегда делайте double-check precondition, а не запускайте refresh сразу после ожидания. Где еще полезно Web Locks хорошо ложится на миграции IndexedDB/localStorage, одноразовую инициализацию кэша, защиту записи в общий browser storage и координацию фоновых задач между вкладками. Ограничения Lock работает только в пределах origin. Это не distributed lock и не замена серверной идемпотентности. Критическая секция должна быть короткой; для сетевого refresh ставьте timeout. Если navigator.locks нет, нужен fallback через BroadcastChannel, storage lease или серверную защиту. Вывод: Web Locks API полезен там, где несколько вкладок делят runtime-состояние: он снижает риск гонок, но не отменяет серверные гарантии.
305
11
День сурка frontend-разработчика Зарплата стоит, скучные задачи день за днем, календарь забит созвонами, которые не влияют во
День сурка frontend-разработчика Зарплата стоит, скучные задачи день за днем, календарь забит созвонами, которые не влияют вообще ни на что. Откликаешься на вакансии, а в ответ тишина либо какие-то мутные конторы. На собесах вместо нормальной оценки навыков цирк с алгоритмами на скорость, как будто ты на олимпиаде, а не работу ищешь. И самое неприятное, пока ты варишься в этом болоте, кто-то спокойно проходит собесы и уходит в Яндекс, VK или на хорошую Валютную удаленку без лишней драмы. Есть классные проекты и сильные команды, где разработчиков действительно ценят, дают расти, поддерживают развитие и платят достойно и ты можешь туда попасть! 👋 Меня зовут Тихон, привет! Я — действующий Frontend-разработчик и ментор. Я за руку довожу до оффера на хорошую позицию в Big Tech и сопровождаю на испытательном сроке. Также из учеников я собираю комьюнити, где уже более 220 frontend-разработчиков🫂 А в своем канале: 👉Объясняю, как проходить HR-фильтр и превращать отклики в реальные приглашения 👉Помогаю найти мотивацию, борюсь убеждениями, которые мешают развиваться 👉На примерах объясняю, как проходить собеседования, включая техничку 👉Разбираю резюме и делюсь лайфхаками, например как аккуратно “пинговать” рекрутеров А еще регулярно публикую полезные материалы: ▪️Задачи, на которых валяться кандидаты ▪️База по микрофронтам ▪️Подборка из 100+ каналов с вакансиями для разработчиков ▪️100 вопросов, которые точно помогут тебе на собеседовании ▪️Чек лист проверки своего резюме А еще у меня множество успешных кейсов и отзывов, найти их можно в канале. Реклама, erid: 2W5zFJeWaNd ИП Галактионов Тихон Витальевич, ИНН 771618975809
236
12
Promise.withResolvers() в production: ручное завершение промисов без deferred-антипаттернов и утечек Promise.withResolvers()
Promise.withResolvers() в production: ручное завершение промисов без deferred-антипаттернов и утечек Promise.withResolvers() полезен на границе event-based API и async/await: WebSocket, Worker, IPC, SDK, API clients, Node.js-сервисы. Частая ошибка - считать его безопасной заменой любому deferred и хранить resolve/reject где попало. Что это даёт const { promise, resolve, reject } = Promise.withResolvers<T>(); Это тот же внешний resolve/reject, но без let, definite assignment и самодельного executor-шаблона. API стал чище, но lifecycle всё ещё ваша ответственность. Где применять * ожидание одного события; * bridge callback/event API в Promise; * request/response поверх WebSocket, Worker или IPC; * внутренняя очередь producer/consumer. Не стоит передавать resolve через слои, класть его в глобальное состояние или создавать promise без timeout/cancel path. Production-паттерн type Message = { id: string; payload: unknown }; type Waiter = { resolve: (m: Message) => void; reject: (e: Error) => void; timeout: ReturnType<typeof setTimeout>; }; const pending = new Map<string, Waiter>(); function waitForMessage(id: string, ms = 5000) { const { promise, resolve, reject } = Promise.withResolvers<Message>(); const timeout = setTimeout(() => { pending.delete(id); reject(new Error(Message timeout: ${id})); }, ms); pending.set(id, { resolve, reject, timeout }); return promise.finally(() => { clearTimeout(timeout); pending.delete(id); }); } Что важно У promise есть owner - waitForMessage(). Есть timeout, очистка через finally(), а resolve/reject не утекают наружу. Иначе Map будет удерживать замыкания, таймеры и payload. Практический совет: если у вызывающего кода есть свой lifecycle, добавьте AbortSignal и снимайте listener в finally(). Вывод: Promise.withResolvers() стоит использовать только там, где явно определены owner, timeout/cancel path и очистка ресурсов.
271
13
Dual package hazard в ESM/CJS: conditional exports без двух копий singleton-состояния в одном Node.js-процессе Это важно для
Dual package hazard в ESM/CJS: conditional exports без двух копий singleton-состояния в одном Node.js-процессе Это важно для SDK, API clients, SSR, Node.js-сервисов и shared libraries. Частая ошибка - считать, что одинаковый TS-код в .mjs и .cjs даст общий runtime. Где ломается const cjs = require('@acme/sdk') const esm = await import('@acme/sdk') Если require попал в dist/index.cjs, а import - в dist/index.mjs, Node загрузит два разных модуля. Итог: * два singleton-инстанса * две registry/cache/map * разные подключения * сломанный instanceof * расходящееся состояние Рабочая схема Один runtime source of truth, второй формат - только фасад. Например, состояние живет в core.cjs, а ESM лишь импортирует его: // package.json "exports": { ".": { "types": "./dist/index.d.ts", "import": "./dist/index.mjs", "require": "./dist/core.cjs" } } // core.cjs const registry = new Registry() module.exports = { Registry, registry } // index.mjs import api from './core.cjs' export const Registry = api.Registry export const registry = api.registry export default api Теперь require и import смотрят на один singleton: cjs.registry === esm.registry // true cjs.Registry === esm.Registry // true Типичная ошибка Не делайте две независимые реализации: "import": "./dist/index.mjs", "require": "./dist/index.cjs" Если оба файла содержат состояние, hazard почти гарантирован. Conditional exports только маршрутизируют загрузку, но не объединяют объекты в памяти. Практический чеклист * закрывайте deep imports вроде @acme/sdk/dist/index.mjs * для каждого subpath, например @acme/sdk/cache, повторяйте ту же схему * для чистых функций риск ниже, для DI, логгеров, метрик, БД-клиентов и кэшей - критичен Вывод: Dual ESM/CJS-пакету нужен один общий runtime-модуль и фасадные entrypoint, а не две сгенерированные копии реализации.
296
14
Совет на ближайшие годы — изучайте ВАЙБ-КОДИНГ ИИ уже пишет код, чинит баги, генерирует тесты, документацию и помогает запуск
Совет на ближайшие годы — изучайте ВАЙБ-КОДИНГ ИИ уже пишет код, чинит баги, генерирует тесты, документацию и помогает запускать продукты быстрее, чем это делали классические команды разработки. И это уже не "будущее когда-нибудь", а реальность, которая меняет рынок уже сегодня И те, кто научится вайбкодить сейчас, будут увереннее конкурировать на рынке и зарабатывать больше тех, кто по-прежнему делает всё вручную. Стартовать с нуля поможет канал Вайб-кодинг. Там ребята круглосуточно мониторят более 320 российских и зарубежных источников и публикуют только главное: релизы, инструменты, гайды, курсы и практические кейсы. Подписывайтесь, нас уже 45 тысяч: @vibecoding_tg
327
15
⁣AsyncLocalStorage в Node.js: request-scoped контекст для логов, трассировки и транзакций без prop drilling В production это нужно в Node.js-сервисах, SSR, API integration и backend-for-frontend слоях, где requestId, traceId, tenant или транзакция должны проходить через async-код. Частая ошибка - протаскивать ctx через десятки методов или, наоборот, прятать в нём бизнес-состояние. Как это выглядит AsyncLocalStorage использует async_hooks и привязывает store к async execution flow: promise, timeout, I/O callback и большинству стандартных API Node.js. type Ctx = { requestId: string; traceId?: string; tx?: unknown }; const als = new AsyncLocalStorage<Ctx>(); app.use((req, _res, next) => { als.run({ requestId: req.headers['x-request-id']?.toString() ?? randomUUID(), traceId: req.headers.traceparent?.toString(), }, next); }); const getCtx = () => als.getStore(); function logInfo(msg: string, meta = {}) { const c = getCtx(); logger.info({ requestId: c?.requestId, traceId: c?.traceId, ...meta, }, msg); } Теперь сервисный код не знает про HTTP middleware и req, но логи автоматически получают correlation metadata. Транзакции без протаскивания tx Для DB слоя можно делать withTransaction(), который запускает als.run({ ...ctx, tx }, fn), а getDbExecutor() возвращает ctx.tx ?? db. Trade-off хороший: меньше шума в API сервисов, но граница ответственности остаётся инфраструктурной, а не бизнесовой. Где границы * не заменяйте аргументы функции: бизнес-данные передавайте явно; * не кладите в store req, res, большие payload или ORM graph; * context не пересекает process boundary: worker threads, очереди, cron jobs и другие сервисы требуют явной передачи ids; * нестандартные callback-based библиотеки могут разорвать async chain. Практическое правило: используйте AsyncLocalStorage для логов, tracing, tenant/user metadata, аудита и текущей транзакции, а не для скрытого глобального состояния. Вывод: AsyncLocalStorage полезен, когда request-scoped инфраструктурный контекст улучшает observability и DX, но не размывает явные границы бизнес-логики.
342
16
АЙТИШНИКИ БЕСПЛАТНОЕ ОБУЧЕНИЕ сборник курсов, инструментов и книг Проект «TERMINAL» стал крупнейшей библиотекой бесплатного о
АЙТИШНИКИ БЕСПЛАТНОЕ ОБУЧЕНИЕ сборник курсов, инструментов и книг Проект «TERMINAL» стал крупнейшей библиотекой бесплатного образования. В одном канале собраны курсы, книги, полезные инструменты и практические тренажёры для всех разработчиков 🎓 Практические курсы и задания 🪽 Книги и статьи известных авторов 😮‍💨 Полезные инструменты и ресурсы 🌟 IT-новости и инсайды Обучение по всем направлениям: SQL, Python, Frontend, PHP, C++, Golang, GIT, Linux, QA, Java, Vibe-coding, Infosec и др. Ценишь знания, подпишись: Terminal_tg
281
17
AbortSignal.any() и AbortSignal.timeout(): единая отмена fetch, таймеров и async-операций в production Promise.race([fetch(),
AbortSignal.any() и AbortSignal.timeout(): единая отмена fetch, таймеров и async-операций в production Promise.race([fetch(), timeout]) часто маскирует проблему: ждать перестали, но работа могла не остановиться. В SPA, SSR, Node.js-сервисах, SDK и API clients это приводит к висящим запросам, таймерам и retry/backoff ниже по стеку. Собирайте отмену вокруг AbortSignal AbortSignal.timeout(ms) сам отменится по таймауту. AbortSignal.any([...signals]) отменится, когда отменится любой входной signal. Так в один контракт попадают: * caller отменил операцию * истек timeout * клиент закрыл соединение * сервис уходит в graceful shutdown const shutdown = new AbortController(); async function loadUser(id: string, opts: { signal?: AbortSignal } = {}) { const signal = AbortSignal.any([ AbortSignal.timeout(1500), shutdown.signal, ...(opts.signal ? [opts.signal] : []), ]); signal.throwIfAborted(); const res = await fetch(`https://api.example.com/users/${id}`, { signal }); await sleep(100, signal); // backoff тоже отменяем const data = await res.json(); signal.throwIfAborted(); return data; } Типичная ошибка Не останавливайтесь на fetch. Один и тот же signal стоит передавать в retry, polling, очереди, sleep/timer helpers и свои async-функции. Иначе верхний слой "отменился", а нижний продолжает держать ресурсы. Практические нюансы * AbortSignal одноразовый: если aborted === true, нужен новый controller или timeout * AbortSignal.any() - это fan-in, а не fan-out: он не отменяет исходные контроллеры * смотрите на reason: timeout часто дает TimeoutError, abort - AbortError или ваш reason * при обертках над setTimeout, stream, listener или socket чистите ресурсы при abort Вывод: Отмена должна быть частью контракта async-функции, а не локальным Promise.race на краю системы.
414
18
Нейросети, IT и AI — в одной папке 💬 С коллегами собрали новые каналы про: 💠 промпты для нейросетей и готовые решения 💠 AI
Нейросети, IT и AI — в одной папке 💬 С коллегами собрали новые каналы про: 💠 промпты для нейросетей и готовые решения 💠 AI-фотосессии, генерация изображений и контента 💠 новости искусственного интеллекта без лишнего шума 💠 применение AI в работе, бизнесе и повседневной жизни 💠 Python, JavaScript, Data Science и системный анализ 💠 вакансии и возможности для специалистов в IT Посмотреть и подписаться тут 👉 https://t.me/addlist/c_rbhnzprbAwMmFi 💌 Добавить свой канал в папку
284
19
Страшная тайна российского айти ✖️ xCode Journal
Страшная тайна российского айти ✖️ xCode Journal
520
20
Хватит гадать — DeepSeek за тебя уже всё решил 🐳 * Сейчас все только про Claude, но я перешёл на DeepSeek и не жалею. Беспла
Хватит гадать — DeepSeek за тебя уже всё решил 🐳 * Сейчас все только про Claude, но я перешёл на DeepSeek и не жалею. Бесплатно, контекст 1 млн токенов — закинул целую книгу, помнит всё. Код пишет отлично, а рассуждения (Reasoning) выдают логику, как у архитектора. Решил протестировать агентский режим на задаче, которую вечно откладывал — собрать чистое инфополе с нуля. Чтобы не перебирать паблики вручную, зашёл через функцию похожих каналов в Telegram. Скормил DeepSeek ссылки на качественных авторов по IT и AI, которых читаю сам, и попросил проанализировать сотни рекомендаций. Агент изучил контент на каналах и оставил только тех, кто делится практическим опытом по: AI-воркфлоу, автоматизации, вайб-кодингу, промт-инжинирингу, RAG-syst. нейрогенерации и др. DeepSeek собрал полезную подборку экспертов в одну папку. Делюсь списком — внутри только полезный контент про IT & AI. 🔗Забирай в один клик: 👉 https://t.me/addlist/FYyQj91I8jJiMzg0
376