1

I am using the fp-ts library and I cannot figure out how to implement the following scenario:

  1. Let's say I have a service with the request method getBooks(shelf, page) and the response looks like this (the request is paginated):
{ 
    totalItems: 100,  
    perPage: 25,  
    books:[{...}, ...],  
    ....
}
  1. So I would like to send an initial request and then calculate the number of pages:
const nrOfPages = Math.ceil(totalItems / perPage);
  1. And then loop to get the rest of the books as the first request will only provide me the first 25 book items.

Now the struggle is that in the end I would like to collect all the books inside one object. Basically, I want to await the results and flatmap them together. It is also important that the requests should be sequential and use the fp-ts library.

const allBooks [{...},{...},{...}, ...];

1 Answers1

1

You can use traverseSeqArray from the Task module to map an array of page numbers into tasks to fetch each page, and each task will be executed sequentially. Then, you can use concatAll (from Monoid) to concatenate the arrays of books.

declare const traverseSeqArray: <A, B>(f: (a: A) => Task<B>) => (as: readonly A[]) => Task<readonly B[]>
declare const concatAll: <A>(M: Monoid<A>) => (as: readonly A[]) => A
import * as M from 'fp-ts/lib/Monoid';
import * as RA from 'fp-ts/lib/ReadonlyArray';
import * as T from 'fp-ts/lib/Task';
import {flow, pipe} from 'fp-ts/lib/function';

declare const getBooks: (
    shelf: Shelf,
    page: number
) => T.Task<{totalItems: number; perPage: number; books: readonly Book[]}>;

const getAllBooks = (shelf: Shelf): T.Task<readonly Book[]> =>
    pipe(
        // Fetch the first page (assuming pages are zero-indexed)
        getBooks(shelf, 0),
        T.chain(({totalItems, perPage, books: firstPageBooks}) => {
            const nrOfPages = Math.ceil(totalItems / perPage);
            // e.g. [1, 2, 3] for 100 books and 25 per page
            const pagesToFetch = Array.from(
                {length: nrOfPages - 1},
                (_, i) => i + 1
            );
            return pipe(
                pagesToFetch,
                // With each page...
                T.traverseSeqArray(page =>
                    // ...fetch the books at the page
                    pipe(
                        getBooks(shelf, page),
                        T.map(({books}) => books)
                    )
                ),
                // Now we have a Task<Book[][]> that we want to turn into
                // a Task<Book[]> including the books from the first page
                T.map(
                    flow(
                        // Prepend the first pages’ books
                        RA.prepend(firstPageBooks),
                        // Concatenate the Book[][] into a Book[]
                        M.concatAll(RA.getMonoid())
                    )
                )
            );
        })
    );

This example assumes that getBooks does not fail, but tihs can easily be modified by switching out Task to TaskEither.

Lauren Yim
  • 12,700
  • 2
  • 32
  • 59