Recently I found the need to make a set of async calls with different return types. This is a pretty common task, we'd like to make some calls in parallel and collect the results once everything is done. Let's take a look at the docs for fp-ts async tasks.



const tasks = [ task . of ( 1 ), task . of ( 2 )] array . sequence ( task )( tasks )() . then ( console . log ) // [ 1, 2 ]

Hmm. Well that's pretty nice, but what happens when the types are different?



const tasks = [ T . task . of ( 1 ), T . task . of ( " hello " )] array . sequence ( task )( tasks )() . then ( console . log ) // [1, "hello"] I hope?

Uh oh.



Well darn. The type of sequence is (simplified) Array[F[A]] => F[Array[A]] , so all the return types would have to be the same.

What do? :/

After some googleing, I ran across the magical sequenceT.



/** * const sequenceTOption = sequenceT(option) * assert.deepStrictEqual(sequenceTOption(some(1)), some([1])) * assert.deepStrictEqual(sequenceTOption(some(1), some('2')), some([1, '2'])) * assert.deepStrictEqual(sequenceTOption(some(1), some('2'), none), none) */

Nice! Ok, let's try it out.



import * as T from ' fp-ts/lib/Task ' import { sequenceT } from ' fp-ts/lib/Apply ' import { pipe } from ' fp-ts/lib/pipeable ' pipe ( sequenceT ( T . task )( T . of ( 42 ), T . of ( " tim " )), //[F[A], F[B]] => F[A, B] T . map (([ answer , name ]) => console . log ( `Hello ${ name } ! The answer you're looking for is ${ answer } ` )) )();

Hello tim! The answer you're looking for is 42

Well that's rad. pipe allows us to chain calls together, so the result of sequenceT is passed in to T.map . T.map destructures the tuple and we can do as we please with some guarantees about our data. But what if our Tasks can fail?



pipe ( sequenceT ( TE . taskEither )( TE . left ( " no bad " ), TE . right ( " tim " )), TE . map (([ answer , name ]) => console . log ( `Hello ${ name } ! The answer you're looking for is ${ answer } ` )), TE . mapLeft ( console . error ) )();

no bad

Awesome! Ok, time to get fancy with it. What if we are actually making some calls to an API, and we want to ensure that the results we get from the API conform to the expected schema?

Let's try it out by hitting a dummy REST endpoint with axios , which is a handy http client.



import { array } from ' fp-ts/lib/Array ' import axios , { AxiosResponse } from ' axios ' ; import * as t from ' io-ts ' //create a schema to load our user data into const users = t . type ({ data : t . array ( t . type ({ first_name : t . string })) }); //schema to hold the deepest of answers const answer = t . type ({ ans : t . number }); //Convert our api call to a TaskEither const httpGet = ( url : string ) => TE . tryCatch < Error , AxiosResponse > ( () => axios . get ( url ), reason => new Error ( String ( reason )) ) /** * Make our api call, pull out the data section and decode it * We need to massage the Error type, since `decode` returns a list of `ValidationError`s * We should probably use `reporter` to make this nicely readable down the line */ const getUser = pipe ( httpGet ( ' https://reqres.in/api/users?page=1 ' ), TE . map ( x => x . data ), TE . chain (( str ) => pipe ( users . decode ( str ), E . mapLeft ( err => new Error ( String ( err ))), TE . fromEither ) ) ); const getAnswer = pipe ( TE . right ( 42 ), TE . chain ( ans => pipe ( answer . decode ({ ans }), E . mapLeft ( err => new Error ( String ( err ))), TE . fromEither ) ) ) /** * Make our calls, and iterate over the data we get back */ pipe ( sequenceT ( TE . taskEither )( getAnswer , getUser ), TE . map (([ answer , users ]) => array . map ( users . data , ( user ) => console . log ( `Hello ${ user . first_name } ! The answer you're looking for is ${ answer . ans } ` ))), TE . mapLeft ( console . error ) )();

Hello George! The answer you're looking for is 42 Hello Janet! The answer you're looking for is 42 Hello Emma! The answer you're looking for is 42 Hello Eve! The answer you're looking for is 42 Hello Charles! The answer you're looking for is 42 Hello Tracey! The answer you're looking for is 42

Heck yeah! We did it! Async Typed FP for everyone! :)