Promise + Either Monad in TypeScript

Nik Vaklev
3 min readJan 28, 2019

Monads are a rather useful way of enforcing some kind of patterns in the code, which are (mathematically) guaranteed to give you particular outcomes.

I spend a lot of my time coding in JavaScript (poor me!) for Node.js. I have found over the last few years that promises can be tricky to use in a consistent way. Especially now, the await/async paradigm in ES2017 probably makes things even more confusing as to what programming style to adopt.

In a recent project I had to make sure asynchronous events were executed in a particular order and if one of them were to fail, the chain will stop executing and report the failure. This approach is useful when, for instance, business logic has to be implemented inside the code, i.e. the requirements are non-technical and somewhat arbitrary. I wrote a profoundly ugly mixture of promises returning Either containers. In the process it struct me that Either and Promise work rather similarly.

This is how the “either” looks like in TypeScript:

class Either<Left, Right = undefined> {
static of<Left, Right>(value: [Left] | [undefined, Right]): Either<Left, Right> {
return new Either<Left, Right>(value);
}
static Right = function<Right>(value: Right) {
return new Either<undefined, Right>([undefined, value]);
};
static Left = function<Left>(value: Left) {
return new Either<Left, undefined>([value]);
};
private value: [Left] | [undefined, Right];constructor(value: [Left] | [undefined, Right]) {
this.value = value;
}
map<T>(func: (val: Right) => T): this | Either<undefined, T> {
return this.value.length === 1 ? this : Either.Right<T>(func(this.value[1]));
}
flatMap<leftT, rightT>(
func: (val: Right) => Either<leftT> | Either<undefined, rightT>
): this | Either<leftT> | Either<undefined, rightT> {
return this.value.length === 1 ? this : func(this.value[1]);
}
either(left: (val: Left) => any, right: (val: Right) => any) {
if (this.value.length === 1) {
return left(this.value[0]);
} else {
return right(this.value[1]);
}
}
}

CAUTION: While the code above is ok, there is more to be desired in terms of static types. Moreover, this is not the only possible implementation, especially the internal workings of the container can be done differently.

The great thing about this monad is that you either get a value or an error message at the end of the chain. No mater how much you map against a “left” value, nothing more will be executed!

Either
.Right(0)
.flatMap( i=> i === 0 ? Either.Left("division by zero") : Either.Right(10/i))
.map(i => i + 1)
.either(
left => {console.log(left); return undefined;},
right => right
)

The main argument for merging Either and Promise is that calling Promise().then can be thought of as equivalent to Either.of().map Moreover, the pattern

Promise()
.then(i => "do something")
.catch(error => "something went wrong")

is very similar to

Either.of(...)
.either(
error => "something went wrong",
i => "do something"
)

In both case you either end up with a value or an error message. Why not marry the two?

Behold the PromisedEither! (work in progress)

class PromisedEither<A, B> {
static Right = <B>(val: B) => {
return new PromisedEither<undefined, B>(Promise.resolve(val));
};
static Left = <A>(val: A) => {
return new PromisedEither<A, undefined>(Promise.reject(val));
};
private value: Promise<A | B>;constructor(promise: Promise<A | B>) {
this.value = promise;
}
map<C>(func: (val: B, resolve: (val: C) => C, reject: (val: A) => A) => void) {
return new PromisedEither<A, C>(
this.value.then(
(val: B) => new Promise<C>((resolve, reject) => func(val, resolve, reject))
)
);
}
either(left: (val: A) => void, right: (val: B) => void): void {
this.value
.then(right)
.catch(left);
}
}

With this baby the following magic absolutely legit:

Either.Right("Good news")
.chain(val => PromisedEither.Right(val))
.map((val, resolve) => resolve(val + '. Excellent!'))
.either(
() => done(),
right => {
assert.equal(right, "Good news. Excellent!");
}
);

Conclusion

Full disclosure, PromisedEither IS syntactic sugar at the end of the day. But, it enforces a particular pattern which would feel natural to developers using mondas already and it gives them a framework rather than an empty canvas to fill in with any code…

My name is Nik Vaklev and I am the founder of Techccino Ltd | Bespoke Business Apps.

--

--