What strategies can I use to steer clear of the pyramid of doom when using chains in fp-ts?

There are times when I encounter a scenario where I must perform multiple operations in sequence. If each operation relies solely on data from the previous step, then it's simple with something like

pipe(startingData, TE.chain(op1), TE.chain(op2), TE.chain(op3), ...)
. However, it becomes complex when op2 also requires data from startingData, leading to nested callbacks.

Is there a better way to structure the code without creating a pyramid of callbacks as shown below?

declare const op1: (x: {one: string}) => TE.TaskEither<Error, string>;
declare const op2: (x: {one: string, two: string}) => TE.TaskEither<Error, string>;
declare const op3: (x: {one: string, two: string, three: string}) => TE.TaskEither<Error, string>;

pipe(
  TE.of<Error, string>('one'),
  TE.chain((one) =>
    pipe(
      op1({ one }),
      TE.chain((two) =>
        pipe(
          op2({ one, two }),
          TE.chain((three) => op3({ one, two, three }))
        )
      )
    )
  )
);

Answer №1

There is indeed a solution to this problem, known as the "do notation". While initially available in fp-ts-contrib, it is now integrated into fp-ts itself using the bind function (applicable to all monadic types). The concept involves binding computation results to specific names and keeping track of these names within a "context" object. Below is an example of how it works:

pipe(
  TE.of<Error, string>('one'),
  TE.bindTo('one'), 
  TE.bind('two', op1), 
  TE.bind('three', op2), 
  TE.bind('four', op3),
  TE.map(x => x.four)  
)

Read on for the Initial Response

I have devised a solution that I'm not entirely satisfied with, but I'm open to feedback!

Firstly, let's establish some helper functions:

function mapS<I, O>(f: (i: I) => O) {
  return <R extends { [k: string]: I }>(vals: R) =>
    Object.fromEntries(Object.entries(vals).map(([k, v]) => [k, f(v)])) as {
      [k in keyof R]: O;
    };
}
const TEofStruct = <R extends { [k: string]: any }>(x: R) =>
  mapS(TE.of)(x) as { [K in keyof R]: TE.TaskEither<unknown, R[K]> };

The mapS function enables the application of a function to all values within an object. TEofStruct, on the other hand, utilizes this function to convert a set of values into an equivalent set of TaskEithers.

My approach involves aggregating new values alongside previous ones utilizing TEofStruct and sequenceS. Currently, the implementation looks like this:

pipe(
  TE.of({
    one: 'one',
  }),
  TE.chain((x) =>
    sequenceTE({
      two: op1(x),
      ...TEofStruct(x),
    })
  ),
  TE.chain((x) =>
    sequenceTE({
      three: op2(x),
      ...TEofStruct(x),
    })
  ),
  TE.chain((x) =>
    sequenceTE({
      four: op3(x),
      ...TEofStruct(x),
    })
  )
);

It seems plausible to create a utility function combining sequenceTE with TEofStruct to minimize redundancy. However, I am still uncertain if this represents the optimal strategy or if there might be a more customary pattern to follow!

Answer №2

If you're interested in the official fp-ts DO notation, it can be found at this link:

However, there's no harm in extracting nested logic to a separate function for ease of understanding.

Similar questions

If you have not found the answer to your question or you are interested in this topic, then look at other similar questions below or use the search

When using Framer Motion for page transitions alongside React Router DOM v6, the layout components, particularly the Sidebar, experience rerenders when changing pages

After implementing page transitions in my React app using Framer Motion and React-Router-DOM, I noticed that all layout components such as the sidebar and navbar were unexpectedly rerendering upon page change. Here's a snippet of my router and layout ...

The NextJS application briefly displays a restricted route component

I need to ensure that all routes containing 'dashboard' in the URL are protected globally. Currently, when I enter '/dashboard', the components display for about a second before redirecting to /login Is there a way to redirect users to ...

Typescript throws an error indicating that the "this" object in Vue methods may be undefined, displaying error code TS2532

As a beginner in question writing, I apologize if my wording is not clear. The issue at hand: I am working on a Vue application with TypeScript. export default { methods: { setProgram: (program: Program)=>{ this.program = progra ...

How can the NavBar be refreshed once a user logs in?

I recently followed a helpful guide on implementing a login system in my React TS application. However, I encountered an issue with the NavBar component within my app compared to how it was coded in the App.tsx file of the guide. Specifically, I found it c ...

The specified function 'isFakeTouchstartFromScreenReader' could not be located within the '@angular/cdk/a11y' library

I encountered the following errors unexpectedly while working on my Angular 11 project: Error: ./node_modules/@angular/material/fesm2015/core.js 1091:45-77 "export 'isFakeTouchstartFromScreenReader' was not found in '@angular/cdk/a11y&a ...

Application: The initialization event in the electron app is not being triggered

I am facing an issue while trying to run my electron app with TypeScript and webpack. I have a main.ts file along with the compiled main.js file. To troubleshoot, I made some edits to the main.js file to verify if the "ready" function is being called. ...

Is it possible to identify unauthorized utilization of web APIs within TypeScript?

Recently, I encountered an issue while using the URLSearchParams.size in my code. To my surprise, it didn't work on Safari as expected. Checking MDN's browser compatibility table revealed that Safari version 16.6 does not support this feature, un ...

Exploring Angular (5) http client capabilities with the options to observe and specify the response type as 'blob'

Situation: I'm facing a challenge in downloading a binary file from a backend system that requires certain data to be posted as JSON-body. The goal is to save this file using File-Saver with the filename specified by the backend in the content-disposi ...

AngularJS - Sending event to a specific controller

I am facing an issue with my page where a list of Leads each have specific actions that are represented by forms. These forms can be displayed multiple times on the same page. Each form has its own scope and controller instance. After submitting a form, an ...

What is the best way to fetch data before a component is rendered on the screen?

I am facing an issue with fetching data from a local server in Node. When I try to render the component, the array 'users' from the state appears to be empty, resulting in no users being displayed on the screen as intended. What's strange is ...

Should FormBuilder be utilized in the constructor or is it considered a poor practice?

section, you can find an example of implementation where declarations for formBuilder and services are done within the constructor(). While it is commonly known that using services inside the constructor() is not a recommended practice and should be done ...

What is the reason behind the ability to reassign an incompatible function to another in TypeScript?

I discovered this question while using the following guide: https://basarat.gitbooks.io/typescript/content/docs/types/type-compatibility.html#types-of-arguments. Here is an example snippet of code: /** Type Heirarchy */ interface Point2D { x: number; y: ...

What causes the oninput event to act differently in Angular as opposed to regular JavaScript?

As I delve into learning Angular with TypeScript, I've encountered some inconsistencies compared to JavaScript that are puzzling me. Take for example this function that works flawlessly with pure JavaScript, where it dynamically sets the min and max a ...

AmCharts stacked bar chart - dynamically adjust value visibility (adjust transparency) based on user interaction

I recently utilized amcharts to construct a bar chart. The creation of my stacked bar chart was inspired by this specific example. Currently, I am attempting to modify the alpha (or color) of a box when hovering over another element on my webpage, such as ...

Implementing TypeScript in an Asp.net Core ReactJS application`s configuration

After using Visual Studio 2022 to create an asp.net core Reactjs project, I discovered that everything was written in javascript instead of typescript. Is there a way to switch this project over to typescript? ...

React 18 update causes malfunctioning of react-switch-selector component

I'm facing an issue where the component is not rendering. I attempted to start a new project but it still didn't work. Is there a solution to fix this problem or should I just wait for an update from the original repository? Encountered Error: ...

What is the process for implementing custom color props with Material-UI v5 in a React TypeScript project?

Looking to enhance the MUI Button component by adding custom color props values? I tried following a guide at , but encountered errors when trying to implement it in a custom component. The custom properties created in createPalette.d.ts did not work as ex ...

Backend server encountered an issue with processing punycode

[ALERT] 18:13:52 Server Restarting Prompt: C:\Code\MERN_Projects\podify_app\server\src\db\index.ts has been altered (node:22692) [DEP0040] DeprecationWarning: The punycode module is outdated. Consider utilizing a modern a ...

Error encountered in Angular2: Attempted to access property 'compilerOptions' which is undefined

I encountered a TypeError: Unable to access the 'compilerOptions' property of undefined Below is the snippet of my compilerOptions code: { "compilerOptions": { "target": "ES5", "module": "commonjs", "emitDecoratorMetadata": tr ...

In React Typescript, the input type="checkbox" does not show any value in the value attribute

I'm facing an issue with displaying text next to a checkbox in React Typescript. When I try to use the value attribute, it doesn't seem to work as expected. Also, attempting to set innerHTML throws an error stating that input is a void element ta ...