конструкция using в грядущем typescript 5.2

В грядущем typescript 5.2 собираются добавить очень интересную и полезную фичу using. Подобное уже реализовано в других языках: В C# это using, в Go - defer. В чем основная идея. using позволяет автоматически освобождать ресурс (вызывать специальный метод для этого) после того, как покидает область видимости блока. Например у нас есть метод getResource у которого реализован метод [Symbol.dispose](). Тогда мы можем написать следующий код и метод Symbol.dispose будет выполнен автоматически в конце выполнения функции main, как если бы эта конструкций была бы завернута в блок try finally. ...

June 27, 2023 · 1 min · 184 words · lexich

Написание адаптера для Fetch API

Кажется, что нет такого проекта, в котором не нужно делать запросы к серверу. И всегда хорошей идеей написать отдельную абстракцию для описания подобной коммуникации. Это добавляет коду не только читаемости и переиспользуемости, но и дает возможность быстро и эффективно написать тест для подобной функциональности. class NetworkService { constructor(private readonly fetch: typeof fetch) {} getUser: (): Promise<IUser> => this.fetch('/api/user').then(d => d.json()) } function getUsername(api: Pick<NetworkService, 'getUser'>) { return api.getUser().then(user => user.name); } test('getUsername', async () => { const name = await getUsername({ getUser: () => Promise.resolve({ name: 'test' } as IUser) }); expect(name).toBe('test'); }); И для среднестатистического проекта такого подхода будет достаточно. Но иногда возникают требования, что нужно переопределить сам fetch. Например, у нас в проекте, мы разрабатываем extension для браузера и хотим делать запросы через serviceWorker. И возникает требования переимплементировать Fetch API. Как мы знаем fetch возвращает примитив Response, для создания которого нужно передать первым аргументом ReadableStream, а вторым характеристики ответа. Таким образом мы можем использовать следующую конструкцию. ...

June 19, 2023 · 2 min · 378 words · lexich

Создание параметризированных строк

Задача, которая встречается почти в каждом проекте - это создание параметризированных строк. Чаще всего подобные конструкции можно встретить роутерах, но применение можно найти во многих других местах. Обычно это выглядит примерно так: const postURL = template('{host}/posts/{id}'); const url = postURL({ host: 'http://example.com', id: 1 }); // url === 'http://example.com/posts/id' Реализовать подобное API не представляет большой сложности. Разбиваем строку с помощью регулярного выражения, подставляя реальные значения вместо переменных. // создаем regexp для разбиения const buildSeparatorVarRx = (start: string, end: string) => new RegExp(`${start}([^${start + end}]+)${end}`); // в качестве разделителя используем скобки const rx = buildSeparatorVarRx('\\{', '\\}'); export function template<T extends string>(tmpl: T) { const array = tmpl.split(rx); // TTemplateFunction - ??? const fn: TTemplateFunction<T> = args => array.map((item, i) => (i % 2 ? (args as Record<string, string | number>)[item] : item)).join('') as any; return fn; } Данная реализация отвечает на все наши запросы, но остается под вопросом что такое TTemplateFunction? Он конструирует из литеральной строки (string literal types) T тип-функцию, которой на вход передается объект, ключи которого должны соответствовать переменным из типа T. Возвращаемое значение будет литеральной строкой с подставленными значениями из аргументов вместо переменных из T. ...

June 4, 2023 · 4 min · 715 words · lexich

DI в typescript в стиле Java

На работе в проекте столкнулись с достаточно распространенной архитектурной проблемой. Есть набор сервисов, зависящих друг от друга. Первоначально взаимосвязи между сервисами описывались c помощью DI через конструктор. interface Options { service1: Service1; service2: Service2; serviceN: ServiceN; } class ServiceNew { constructor(private options: Options) {} } В большинстве случаев такой подход является достаточно эффективным. Однако порой возникают ситуации, например, когда 2 сервиса зависят друг от друга и приходится инжектить зависимость через функции-геттеры. class Sevice1 { constructor( private getService2(): Service2, private serviceN: ServiceN ) {} } class Sevice2 { constructor( private getService1(): Service1, private serviceN: ServiceN ) {} } В ситуации когда таких сервисов например 20, то хочется уже более единообразного подхода. Например мы можем обратиться к опыту языка Java и сохранить все сервисы в один объект и передать ссылку на него в каждый сервис. А для большей изоляции кода можно на уровне типов ограничить список используемых сервисов. Главное условие при таком подходе, что нельзя обращаться к такому объекту в конструкторе, тк в этот момент еще идет наполнение этого объекта. При этом остается декларативность в описании зависимостей и сохраняется единообразность подхода. Также если в каком-то сервисе мы вдруг забудем про объявлении поля ctx, то type-checker об этом напомнит. ...

May 31, 2023 · 2 min · 348 words · lexich

Асинхронные операции в mobx

Продолжаем нашу серию заметок: “Готовим mobx в обход документации”. В прошлый раз мы реализовали примитив Subscriber. Он очень полезный, но не у каждого в проекте есть вебсокеты или подобные генераторы данных. Что есть у каждого, так это REST API. Работа с асинхронными операциями в документации, хоть и описана, но с архитектурной стороны вызывает одни подозрения и много boilerplate кода. Есть пакет mobx-utils, который содержит метод fromPromise, но очень непредсказуемо, если обратиться к нему вне реактивного контекста и то можно получить тонну запросов на сервер. ...

May 25, 2023 · 2 min · 330 words · lexich