Writing an Adapter for Fetch API

It seems that there is no such project that does not need to make requests to the server. And it is always a good idea to write a separate abstraction to describe such communication. This not only adds readability and reusability to the code, but also makes it possible to quickly and efficiently write tests for such functionality. 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'); }); And for an average project, this approach will be sufficient. But sometimes there are requirements that you need to redefine the fetch itself. For example, in our project, we are developing an extension for the browser and we want to make requests through the serviceWorker. And there is a requirement to reimplement the Fetch API. As we know, fetch returns a Response primitive, for the creation of which you need to pass the ReadableStream as the first argument, and the response characteristics as the second. Thus, we can use the following construction. ...

June 19, 2023 · 3 min · 437 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

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

Java-style DI in TypeScript

At work, we encountered a fairly common architectural problem in our project. We have a set of services that depend on each other. Initially, the relationships between the services were described using DI through the constructor. interface Options { service1: Service1; service2: Service2; serviceN: ServiceN; } class ServiceNew { constructor(private options: Options) {} } In most cases, this approach is quite effective. However, sometimes there are situations, for example, when two services depend on each other and you have to inject the dependency through getter functions. ...

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

Migration in React from Context API to Mobx Part 2

Continuing yesterday’s topic about migration to mobx, the question reasonably arises, why migrate to anything at all, if there is redux, context api, useReducer and all sorts of similar solutions on which React development is now built. export const AppContext = createContext({}); export const AppContextProvider: FC = ({ children }) => { const [user, setUser] = useState<IUser>(undefined); const [profile, setProfile] = useState<IProfile>(undefined); const setUserId = (id: string) => loadUser(id).then(setUser); useEffect(() => { if (user) { loadProfile(user).then(setProfile); } else { setProfile(undefined); } }, [user]); return ( <AppContext.Provider values={{ user, profile, setUserId }}> {children} </AppContext.Provider>); } const Component: FC = () => { const { profile } = useContext(AppContext); return <div>{profile.content}</div> } Let’s imagine a virtual example that is quite simple to find in many projects. What’s wrong with it? Let’s do a thought experiment. We have a component that displays profile. How many times will our component re-render when we call setUserId. The first re-render will be on setUser, then useEffect will be called. I would never use it for state management. Next, setProfile and this is the second re-render. The same behavior will be in every component that uses this context. And suboptimality is only half the trouble, but if, for example, we want a small test to check how it all works… I don’t even want to think about it and move to the camp of those who don’t write tests and only talk about them at interviews. ...

May 12, 2023 · 2 min · 407 words · lexich