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

Настоящий JavaScript

Ir al canal en Telegram

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

Mostrar más
6 147
Suscriptores
-324 horas
-317 días
-16230 días
Atraer Suscriptores
julio '26
julio '26
+5
en 0 canales
junio '26
+110
en 0 canales
Get PRO
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
14 julio0
13 julio+1
12 julio0
11 julio0
10 julio+1
09 julio0
08 julio0
07 julio0
06 julio+1
05 julio0
04 julio0
03 julio0
02 julio+1
01 julio+1
Publicaciones del Canal
TAMA-90: собираем тамагочи из 90-х на чистом JavaScript До того, как автор сел писать материал, он даже не парился насчет тог
TAMA-90: собираем тамагочи из 90-х на чистом JavaScript До того, как автор сел писать материал, он даже не парился насчет того, что такое тамагочи. Слово мелькало в кино и сериалах, но реально оно в голове не отложилось. Самый втык — сцена из «Менталиста», где Патрик Джейн вручает коллеге эту игрушку, а тот, взрослый мужик, аж размяк от воспоминаний о детстве. Поначалу автор считал тамагочи просто очередным девайсом прошлого вроде Game Boy или Dendy. Но со временем дошло: если игрушку продолжают упоминать спустя три десятилетия и кучу реинкарнаций — тут явно не просто так. Автор полез разбираться, что это за зверь и как он устроен. Для начала — матчасть, потому что без нее вспоминать старые темы бессмысленно (все, что старше 10 лет, уже по умолчанию «ретро», без обид, надеемся). Читать далее 👉 JS

2
Директ возвращает 10% бюджета за рекламу в мессенджерах Запускайте кампании в Telegram и МАКС с 1 июня по 31 августа — получа
Директ возвращает 10% бюджета за рекламу в мессенджерах Запускайте кампании в Telegram и МАКС с 1 июня по 31 августа — получайте кешбэк 10%. Его можно потратить на новые кампании. 💰 Как получить кешбэк: — Заполните форму: укажите логин в Директе и что продвигаете (сайт или канал); — Запустите рекламу в МАКС, Telegram или сразу на двух площадках с оплатой за клики; — В сентябре начислим кешбэк. Получить предложение #реклама yandex.ru О рекламодателе
96
3
⁣Теневые утечки памяти в AsyncLocalStorage + Promise.allSettled: баг, который вы не заметите до продакшена Вы используете AsyncLocalStorage для контекста запроса, обрабатываете пачку промисов через Promise.allSettled — и вроде всё работает. Но через несколько часов нагрузочного тестирования память растёт, GC не успевает, а контекст в некоторых тасках начинает «перемешиваться». Знакомо? Типовой баг в production Вот как выглядит код, который ломается под нагрузкой: import { AsyncLocalStorage } from 'async_hooks'; const storage = new AsyncLocalStorage(); async function handler() { storage.run({ userId: 1 }, async () => { const results = await Promise.allSettled([ fetchUser(2), fetchUser(3), ]); results.forEach((r) => { if (r.status === 'fulfilled') { // Иногда storage.getStore() возвращает undefined или чужой userId console.log(storage.getStore()?.userId); } }); }); } AsyncLocalStorage привязывается через async_hooks. Promise.allSettled создаёт новые микрозадачи, которые могут «отцепиться» от родительского контекста, если между ними есть синхронный код. В production при 50 параллельных запросах это даёт 0.1-1% перемешивания контекста — незаметно в unit-тестах, но критично для авторизации. Утечка памяти При каждом storage.run() создаётся новый экземпляр хранилища. Promise.allSettled с 1000 внутренних промисов сохраняет ссылки на контекст через async_hooks — GC освобождает память только после завершения всех промисов. Если один из них «зависает» из-за необработанного reject в обработчике then/catch, утечка растёт бесконечно. В Node 14-16 это системная проблема, воспроизводимая через 1000 параллельных вызовов handler() с 10 подзапросами внутри. Практический совет Если не можете обновить Node.js до 20+ (где async_hooks исправлены), избегайте Promise.allSettled внутри storage.run(). Вместо этого разбейте на отдельные вызовы run для каждого запроса. Для критических мест передавайте контекст через параметры функции, а не через глобальное хранилище — это устраняет и утечки, и перемешивание. Предупреждение Типичная ошибка — считать, что AsyncLocalStorage гарантированно возвращает правильный контекст в любом асинхронном коде. На деле Promise.allSettled с его микрозадачами нарушает это в старых версиях Node. Не доверяйте глобальному контексту без проверки: добавляйте логирование storage.getStore() в ключевых точках под нагрузкой. Вывод: Promise.allSettled и AsyncLocalStorage несовместимы в старых версиях Node — проверяйте свою среду и передавайте контекст явно, чтобы избежать багов, которые невозможно отловить в unit-тестах.
112
4
OpenAI снова играется со своими игрушками — Codex и ChatGPT Work получили апдейты 🚀 Компания подкрутила инференс, а сэкономл
OpenAI снова играется со своими игрушками — Codex и ChatGPT Work получили апдейты 🚀 Компания подкрутила инференс, а сэкономленные бабки решили не прятать в карман, а раздать всем, кто сидит на GPT-5.6 Sol. Так что теперь доступный объем юзанья подрастет примерно на десятку процентов. Еще они заметили, что когда разогнали контекстное окно с 272к до 372к токенов, тарификация начала слетать с катушек. Временно откатили обратно до 272к — мол, скоро снова запустят 372к, но уже с обещанием, что лимиты будут тратиться куда медленнее. Плюс на high и xhigh уровне reasoning мультиагентная обработка жрала больше ресурсов, чем планировали — баг уже фиксят. И еще один момент в авто-превью тоже дорабатывают. А для Codex сегодня разблокировали лимиты и временно сняли пятичасовые ограничения для Plus, Business и Pro 😁 👉 JS
123
5
⁣AbortSignal.timeout() убивает Worker до старта: неочевидные race condition при вложенных сигналах Передаёшь AbortSignal в Worker через postMessage и ждёшь abort как гарантию отмены? Типичная ловушка: сигнал с таймаутом стартует в момент создания, а не когда Worker его получает. Результат — Worker грузится дольше таймаута и получает уже aborted: true, не выполнив ни строчки кода. Таймаут живёт в main thread, Worker получает мёртвую копию const controller = new AbortController(); const timeoutSignal = AbortSignal.timeout(5000); const combined = AbortSignal.any([controller.signal, timeoutSignal]); worker.postMessage({ type: 'start', signal: combined }); Таймер из AbortSignal.timeout() не Transferable. Сигнал передаётся как структурированная копия состояния, но оригинал остаётся в main thread. Если таймаут срабатывает после передачи — Worker никогда не узнает об этом. Memory leak гарантирован, так как замыкание с обработчиком abort не очищается. Нельзя сбросить сработавший сигнал Когда AbortSignal.timeout() abort'нул внешний сигнал через AbortSignal.any(), контроллер становится мёртвым навсегда: controller.signal.addEventListener('abort', () => console.log('aborted')); timeoutSignal.dispatchEvent(new Event('abort')); controller.abort(); // 0 реакций, контроллер уже не активен Это race condition, который не ловится без логов: ты пытаешься вызвать controller.abort() позже, но сигнал уже в состоянии aborted: true от таймаута. Практический совет для Workers Создавай AbortSignal.timeout() прямо внутри Worker, а не передавай снаружи: // Внутри Worker self.onmessage = ({ data }) => { const timeout = AbortSignal.timeout(5000); if (data.signal.aborted) return; timeout.addEventListener('abort', () => controller.abort()); }; Перед стартом работы явно проверяй if (signal.aborted) return. Используй один центральный AbortController для всех вложенных сигналов вместо цепочек через AbortSignal.any(). Это снижает риск невидимых race condition при комбинировании таймаутов. Вывод: AbortSignal.timeout() хорош для простых таймеров в одном потоке, но при передаче в Workers и комбинировании сигналов требует явной проверки состояния и изоляции создания таймаута внутри получателя.
141
6
Онлайн-магистратура для IT: ИТМО, МИФИ + Яндекс Программы онлайн-магистратуры ИТМО и МИФИ в партнёрстве с Яндексом. Актуальны
Онлайн-магистратура для IT: ИТМО, МИФИ + Яндекс Программы онлайн-магистратуры ИТМО и МИФИ в партнёрстве с Яндексом. Актуальные знания, практическое обучение и гибкий график. Учитесь, совмещая с работой. Доступна господдержка оплаты, отсрочка от армии Перейти на сайт #реклама 16+ practicum.yandex.ru О рекламодателе
152
7
TypeScript 7 переписали на Go — разбираемся, реально ли он в десять раз шустрее В блоге TypeScript заявляют: «Проверка типов
TypeScript 7 переписали на Go — разбираемся, реально ли он в десять раз шустрее В блоге TypeScript заявляют: «Проверка типов для проекта VS Code (под 1,5 млн строк) упала с 77.8 с до 7.5 с». Команда сразу вывела в заголовок суть — «примерно в 10 раз быстрее». Тип решил воспроизвести один график — не на той же кодовой базе Microsoft, а на монорепо, скроенном специально для теста, двумя компиляторами на одной тачке. Каждая цифра подтверждена, репозиторий лежит — можно запустить pnpm bench и самому глянуть. Спойлер: да, TS7 реально быстрее, и это заметно. Только вот «в десять раз» честно работает в одном сценарии, в другом уже хрен да маленько, а кое-что важное в 7.0 пока не завезли — и это «кое-что» для многих важнее любой скорости. Суть простая: «10×» — это про проверяльщик на здоровом проекте, а не про всю вашу рабочую рутину. TS7 резко ускоряет проверку типов и меньше жрет память, бенчмарки это подтверждают. Но в седьмой версии нет стабильного программного API — и вместе с ним из сборки вылетают кастомные трансформеры, плагины для tsserver и типизированный линтинг. Так что вопрос миграции не «быстрее ли стало?», а «завязана ли твоя сборка на этот API». Читать далее 👉 JS
181
8
Утраиваем бюджет на продвижение в Директе Запустите первое продвижение в Яндекс Директе с утроенным бюджетом и ИИ-помощником
Утраиваем бюджет на продвижение в Директе Запустите первое продвижение в Яндекс Директе с утроенным бюджетом и ИИ-помощником ✨ Используйте один из промокодов : При пополнении от 10 000 ₽ +20 000 ₽ Промокод START20 При пополнении от 15 000 ₽ +30 000 ₽ Промокод START30 Зарегистрироваться #реклама direct.yandex.ru О рекламодателе
236
9
🤣 Мы все ждали когда это начнётся ✖️ xCode Journal
🤣 Мы все ждали когда это начнётся ✖️ xCode Journal
251
10
Строите планы на лето? Кажется, лучше выбирать загар. А ещё лучше — со скидкой 5000 рублей по промокоду NETSIL на бронировани
Строите планы на лето? Кажется, лучше выбирать загар. А ещё лучше — со скидкой 5000 рублей по промокоду NETSIL на бронирование жилья на Яндекс Путешествиях. Забронировать #реклама travel.yandex.ru О рекламодателе
216
11
⁣User-Defined Type Guards vs Branded Types: скрытые costs производительности и баги с узкими типами В больших codebase на TypeScript type guards и branded types часто создают излишнюю уверенность в типах, скрывая реальные рантайм-проблемы и влияя на производительность. Ошибки возникают при их использовании в production: фронтенд-приложения с API-клиентами, серверная валидация на Node.js, shared libraries — везде, где надо отделить один тип от другого. Проблема с type guards TypeScript “верит” твоему предикату, не проверяя его корректность внутри. После прохождения guard он забывает о других типах, что может привести к багам. function isString(value: unknown): value is string { return typeof value === 'string'; } function process(data: string | number | null) { if (isString(data)) { return data.toUpperCase(); // data: string, но если guard не учел null, рантайм баг } } Типичная ошибка: guard не обрабатывает null или undefined. В больших унионах это “съедает” целые ветки, и баг уходит в прод. Branded types: иллюзия безопасности Типа type Email = string & { __brand: 'email' } — это попытка номинальной типизации. Но это работает только на уровне типов, без runtime-гарантий. type Email = string & { __brand: 'email' }; function isEmail(v: string): v is Email { return v.includes('@'); } // Любая строка с '@' считается Email, но brand отсутствует в runtime В большом проекте один неверный guard — и цепочка типов ломается, особенно при пересечениях с generics. Компилятор тупит на inference, что замедляет сборку и увеличивает время анализа. Performance trade-offs 1. Кастомные guards не inlined — лишние вызовы в горячих путях (циклы, часто вызываемые функции) и growth bundle. 2. Branded types в generics приводят к замедлению type inference при вложенности. 3. Для реальной безопасности всё равно нужны runtime-проверки, которые дублируют логику и увеличивают latency. Совет: используй простые встроенные проверки (typeof, instanceof) для простых типов. Branded types оборачивай в конструкторы с runtime-валидацией: function createEmail(v: string): Email { if (!v.includes('@')) throw new Error(); return v as Email; } И обязательно покрывай guards unit-тестами на граничные значения: null, undefined, пустые строки — это 30% багов с типами. Вывод: Type guards и branded types — инженерные компромиссы, где безопасность типов в compile-time стоит runtime-производительности и скрытых багов, если не добавлять параллельные runtime-проверки.
299
12
⁣WeakMap и WeakSet для безопасного кэширования в production: неочевидные утечки через closured references и производительность при частой сборке мусора Если вы храните кэш в Map внутри замыкания, то даже после того как объект-ключ становится недоступен, ссылка остается — объект не собирается GC. В больших SPA, API-клиентах или SSR-рендерах это приводит к постепенному росту памяти без видимой причины. Почему WeakMap решает проблему утечек Замена Map на WeakMap автоматически удаляет запись при исчезновении последней внешней ссылки на ключ. Это критично для кэширования результатов при обработке временных объектов, например, в middleware или event handlers. function createWeakCache() { const cache = new WeakMap(); return { get: (obj) => cache.get(obj), set: (obj, value) => cache.set(obj, value) }; } Подводный камень: GC при высокой частоте ключей WeakMap/WeakSet очищаются только во время сборки мусора. Если вы создаете короткоживущие объекты (меньше 100 мс жизни) и сразу добавляете их в WeakMap, то GC будет запускаться чаще — до десятков раз в секунду. Это проседает CPU в Node.js API или FPS в браузере. * Плохой паттерн — каждый микротик создавать объекты и помещать их в WeakSet. * Пример: seen.add(obj) в цикле генерации 1000 объектов за 1 мс — GC будет чистить постоянно. Практический совет и проверка 1. Не используйте WeakMap/WeakSet для high-frequency cache (миллионы ключей за секунду). Лучше обычный Map с ручной очисткой или LRU-стратегией. 2. Проверяйте профиль GC: Chrome DevTools > Performance > Memory. Если видите частые малые пики GC при короткоживущих объектах — пересмотрите архитектуру. 3. Для долгоживущих объектов (DOM-ноды, классы с временем жизни > 1 секунды) WeakMap безопасен и выгоден. Производительность Операции в WeakMap в 2-4 раза медленнее Map (микросекунды). Но ключевая стоимость — GC: чем больше ключей без внешних ссылок, тем чаще GC проверяет их. Это может добавить десятки миллисекунд на сборку при нескольких тысячах ключей. Вывод: WeakMap и WeakSet — мощный инструмент против утечек через замыкания, но при high-frequency кэшировании короткоживущих объектов вы рискуете получить нагрузку на GC, а не спасение памяти.
221
13
⁣Сидеть и работать в корпорации страшно, жизнь-то мимо проходит. Уходить строить бизнес страшно, а вдруг прогорит. Один из вариантов - разрабатывать свой пет-проект по вечерам. Многие успешные компании, например, Twitter, создавались именно так. Это не значит, что ваш проект обязательно заработает миллиарды, но заработать больше, чем в найме, и получить ценный опыт вполне реально. Перед началом разработки появляется множество вопросов, например: – Как выбрать идею для пет-проекта? – Что нужно знать про маркетинг? – Как запуститься и довести до первых продаж не имея бюджета на рекламу? В телеграм-канале Твой пет проект, Михаил Табунов делится своим опытом с разработчиками и менеджерами. Он рассказывает, где искать идею для нового проекта, что нужно знать о маркетинге, как запустить стартап и привлечь первых 10 клиентов, а также о многих других важных вещах. Подписывайтесь на Твой пет проект получайте пользу от практиков рынка!
197
14
⁣AbortSignal.timeout и AbortController.any: гонки, утечки и производительность при композиции асинхронных операций Эти API кажутся удобными для таймаутов и отмены, но в production под нагрузкой они порождают неочевидные баги: двойные вызовы обработчиков, утечки памяти и потерю контроля над ресурсами. Особенно это заметно в Node.js сервисах с высокой конкуренцией запросов или в SPA с частыми опросами. Классический таймаут: скрытая утечка async function fetchWithTimeout(url, timeoutMs) { const signal = AbortSignal.timeout(timeoutMs); return fetch(url, { signal }); } Запрос завершился за 50 мс, а таймаут на 5000 мс ещё активен. В Node 18+ сигнал очищается автоматически, но в браузерах, особенно Safari, слушатель abort может висеть до срабатывания. При 1000 таких вызовов — привет, рост памяти и недетерминированные гонки при отписке. AbortController.any: двойной abort const combined = AbortController.any([ controller.signal, AbortSignal.timeout(5000) ]); Первый сигнал сработал, создался composite signal. Если второй догоняет до фактической отписки — по спецификации флаг aborted должен быть один, но на практике реализации в разных средах (Edge, Safari) кидают событие abort дважды. Обработчик отрабатывает два раза: гонка в бизнес-логике и лишний вызов колбэка. Production-практика: контроль через ручной таймер Для горячих путей (частые запросы, опросы графиков) лучше завести один AbortController и таймер через setTimeout с явным reject: const controller = new AbortController(); const timer = setTimeout(() => controller.abort(), timeoutMs); try { const result = await fetch(url, { signal: controller.signal }); return result; } finally { clearTimeout(timer); } Так вы сами управляете жизненным циклом таймера и избегаете лишних слушателей. Минус — больше кода, плюс — предсказуемое поведение без гонок. Вывод: Иногда старый setTimeout с ручной очисткой оказывается надёжнее встроенного AbortSignal.timeout, особенно под нагрузкой с частыми вызовами и конкурентными таймаутами.
247
15
Анатомия парного доклада: как мы собирали «PROvoke» на Analyst Days. Часть 2 — выступление и фидбек Привет, Хабр! В первой ча
Анатомия парного доклада: как мы собирали «PROvoke» на Analyst Days. Часть 2 — выступление и фидбек Привет, Хабр! В первой части я рассказывал, как мы с Татьяной Маркиной готовили доклад «Провоок» на Analyst Days — про споры, студию в Краснодаре, логистический ад и идею с кнутом и пряником. А сегодня — второй части: что произошло на сцене, какие роли мы играли, почему корсет так и не вышел в свет, и как один разговор в кулуарах перевернул моё понимание публичных выступлений. Читать далее
270
16
⁣Intl.ListFormat и Intl.DateTimeFormat: как локализация ломает SSR и гидратацию Локализация через Intl API обычно кажется безобидной, пока не сталкиваешься с расхождениями между сервером и клиентом. В production на SSR React, Next.js или Remix race condition по локали между серверным рендерингом и гидратацией — частая причина трудновоспроизводимых багов, особенно в приложениях с мультиязычностью и SEO. Race condition по локали Сервер получает локаль из хедера Accept-Language, а клиент — из navigator.language. Если на сервере сформировали Intl.ListFormat('de').format(['a', 'b', 'c']) (немецкий "a, b und c"), а гидратация пошла с английским "a, b, and c", React выдаёт warning о несовпадении. Фиксируй локаль глобально до гидратации, например через Intl.DateTimeFormat.supportedLocalesOf и ручной выбор. Локаль по умолчанию — ловушка new Intl.DateTimeFormat() в Node.js без аргументов использует en-US. На клиенте с русской локалью браузер выдаёт DD.MM.YYYY. Разница в форматировании — гарантированный hydration mismatch. Всегда передавай локаль явно: new Intl.DateTimeFormat('ru-RU'). Иначе гидратация поломается будет warning. Типичная ошибка: мокинг только одного API В тестах часто мокают только DateTimeFormat, забывая ListFormat. Это приводит к тому, что список "1, 2, 3" сервер превратит в "1, 2 und 3", а клиент — в "1, 2, and 3". Мокай оба API сразу через jest.mock('intl', () => ({ ... })) или вообще выноси локализацию в отдельный слой, который заменяется целиком. Практический совет: кешируй инстансы Каждый new Intl.DateTimeFormat('ru') создаёт объект с внутренним состоянием. На SSR с сотнями запросов GC не успевает, появляются лаги. Используй кеш: const cache = new Map(); cache.get(locale) || cache.set(locale, new Intl.DateTimeFormat(locale)).get(locale). Это снижает нагрузку на память и ускоряет рендеринг. Предупреждение: порядок слов ломает гидратацию Intl.ListFormat('ja').format(['x', 'y', 'z']) даёт японский формат с иероглифом-разделителем. Сравнивать строки на сервере и клиенте без учёта локали — сразу mismatch. Проверяй, что локаль одинакова, иначе hydration failed. Вывод: Явная локализация, кеширование инстансов и одновременный мокинг Intl.ListFormat и Intl.DateTimeFormat — ключевые практики для надёжной локализации в SSR и тестах.
256
17
⁣Temporal API: неочевидные edge cases с часовыми поясами, границами дней и производительность в production Temporal — долгожданная замена Date и moment.js, но она не делает магию, а лишь делает её предсказуемой. В production три сценария могут сломать логику: DST-разрывы, ложные сравнения и скрытые аллокации. 1. Граница дня — это не 24 часа: DST создаёт несуществующие моменты Перевод часов в часовом поясе с летним временем — классическая ловушка. Попытка создать ZonedDateTime с несуществующим временем, например 02:30 в America/New_York при переходе на DST: const zdt = Temporal.ZonedDateTime.from({ year: 2024, month: 3, day: 10, timeZone: 'America/New_York', hour: 2, minute: 30 }); // RangeError: момент не существует Решение — опция { disambiguation: 'earlier' } или 'later'. Но 'later' даст 03:30 вместо ожидаемых 02:30. Логика с startOfDay() обязана это учитывать. Иначе баг воспроизводится раз в полгода. 2. Сравнение ZonedDateTime: equals() игнорирует часовой пояс equals() сравнивает календарные поля, а не абсолютное время: const a = Temporal.ZonedDateTime.from('2024-01-01[UTC]'); const b = Temporal.ZonedDateTime.from('2024-01-01[Asia/Tokyo]'); a.equals(b); // true — часы и даты совпадают, но момент разный a.epochMilliseconds === b.epochMilliseconds; // false — физически разные моменты Это ошибка: в [UTC] и [Asia/Tokyo] один и тот же календарный день — это разные моменты, отстоящие на +9 часов. Для точного сравнения всегда приводите к Instant или используйте .since(). Иначе половина пользователей получит неверные расписания. 3. Производительность: Temporal не бесплатен — профилируйте Каждый ZonedDateTime.from() с нестандартным часовым поясом парсит базу IANA TZif — это поиск по файлу. .until() / .since() не ленивы: сразу вычисляют разницу по всем полям. А toPlainDate() не кешируется — каждый вызов аллоцирует новый объект. На 100k+ событиях в секунду с разными таймзонами замена Date на Temporal дала рост CPU на 40% на Node.js 20. Для редких вычислений Temporal отличен, но для hot path используйте Intl.DateTimeFormat + Date.getTime(). Вывод: Temporal решает 90% старых проблем, но DST-разрывы, сравнение по календарю и неучтённые аллокации — ваши новые враги; профилируйте и тестируйте на границах дней с реальными данными.
252
18
Как я хакнул рынок труда: пишем свой ИИ-комбайн для автооткликов на HH.ru Всем привет! Если вы хоть раз искали работу в IT за последний год, то знаете, что рынок беспощаден к новичкам. Нужно откликнуться на сотни вакансий, а в итоге получаешь отказы от роботов. Чтобы пробиться через фильтры HR, нужно под каждую вакансию писать уникальное сопроводительное письмо. В этой статье я сделаю полный разбор того, как я написал собственного автономного ИИ-агента, который ищет вакансии, фильтрует мусор с помощью локальной нейросети, пишет персонализированные сопроводительные письма и отчитывается мне в Telegram, пока я спокойно занимаюсь своими делами. Я хотел, чтобы скрипт был бесплатным, автономным и не требовал танцев с бубном вокруг платных API. Читать далее на Habr
300
19
⁣Branded Types в TypeScript: как заставить типы работать в рантайме Типы в TypeScript исчезают при компиляции, но в production мы сталкиваемся с данными из внешнего мира, где структурная типизация не способна защитить от путаницы между двумя сущностями одного базового типа — например, Email и UserId, объявленными как строки. Branded Types с Zod-схемами позволяют сохранить семантику в compile-time и runtime-валидацию для production-кода. Проблема структурной типизации Структурная типизация TypeScript не отличает Email от UserId, если оба — строки. Это приводит к ошибкам, когда в функцию отправки письма передаётся идентификатор пользователя. Branded Types добавляют невидимую метку на уровне типов, но не обеспечивают проверку в рантайме. Zod даёт реальную валидацию при работе с API, формами или env. Решение с Zod и branded types Используйте Zod для парсинга данных, извлекая типы через z.infer и добавляя brand-метку. Пример: import { z } from 'zod'; const EmailSchema = z.string().email().brand('Email'); type Email = z.infer<typeof EmailSchema>; function sendEmail(to: Email) { /* */ } const email = EmailSchema.parse('user@example.com'); sendEmail(email); // compile-time + runtime pass Типичная ошибка — попытка передать сырую строку напрямую в sendEmail. Branded type отсечёт это на этапе компиляции, а схема Zod перехватит некорректные данные из API ещё до логики. Где применять и trade-offs Подходит для моделей UserId, OrderId, SKU, чтобы избежать путаницы в API-клиентах, SDK или легаси-миграциях. Но: - Не защищает от злонамеренной подделки — это не криптография, а дисциплина типов. - Легко переборщить: для простых DTO достаточно обычных структурных типов. - Без командных договорённостей бренды становятся кашей — кто-то использует Zod, кто-то самописные guards. Практический совет: ограничьте бренды только критичными сущностями на границе модулей. Вывод: Branded Types с Zod решают проблему runtime-гарантий для доменных моделей за счёт дисциплины типов и zero-cost метки, но требуют строгих конвенций в команде.
267
20
⁣Изящный баг в Edge Runtime: prototype pollution через structuredClone и Symbol-свойства Вы доверяете structuredClone защиту от прототип-поллюшена? Зря. В Edge Runtime старых версий (V8) баг позволяет Symbol-ключам с именем __proto__ при клонировании записаться как строковые ключи, обходя стандартные проверки. Это особенно опасно в API-серверах на Node.js и edge-функциях, где входящие данные не проходят валидацию. Как это работает Обычная защита от prototype pollution строится на проверке ключей через Object.keys() или for...in. Но эти методы игнорируют Symbol-ключи. Злоумышленник вставляет символ с именем __proto__: const payload = { [Symbol('__proto__')]: { polluted: true } }; const clean = structuredClone(payload); В уязвимых версиях Edge Runtime этот символ неправильно обрабатывается как строковой ключ, и объект clean получает прототип-загрязнение. Почему это изящно - Object.keys() и for...in не видят Symbol-ключи — стандартная проверка их пропускает. - JSON.parse(JSON.stringify()) теряет Symbol — многие разработчики перешли на structuredClone, считая его безопаснее. - Но structuredClone обрабатывает Symbol-ключи (кроме well-known). Механизм безопасного клонирования становится вектором атаки через незаметные Symbol. Типичная ошибка Думать, что structuredClone изолирует от prototype pollution, если вы проверяете только строковые ключи. В code review я встречал код, где после глубокого клонирования входных данных объект проверялся Object.keys() — Symbol остались незамеченными. Production-совет Для временных данных из ненадёжных источников используйте Object.create(null) — он блокирует прототипную цепочку. Дополнительно, проверяйте Symbol-ключи вручную: Object.getOwnPropertySymbols(obj). Если вы используете edge runtime — обновляйте его до актуальной версии, где этот баг исправлен. Вывод: structuredClone не является панацеей от prototype pollution — Symbol-ключи с именами __proto__ могут обходить защиту, если вы не обрабатываете их явно.
275