UNPKG

data.task.ts

Version:

Personal functional Task typeclass implementation

299 lines 12.3 kB
"use strict"; Object.defineProperty(exports, "__esModule", { value: true }); exports.Task = void 0; const monet_1 = require("monet"); const synchronize = (rej, res) => { let fn = null; let val = null; let rejected = false; const rejecter = (err) => { if (rejected) return; rejected = true; rej(err); }; const resolver = (setter) => (x) => { if (rejected) return; setter(x); if (fn != null && val != null) res(fn(val)); }; return { guardReject: rejecter, resolveFn: resolver((f) => { fn = f; }), resolveValue: resolver((v) => { val = v; }), }; }; const taskToTaskOfEither = (t) => new Task((_, res) => { t.fork(err => res(monet_1.Either.Left(err)), succ => res(monet_1.Either.Right(succ))); }); /** * The `Task<E, R>` structure represents values that depend on time. This * allows one to model time-based effects explicitly, such that one can have * full knowledge of when they're dealing with delayed computations, latency, * or anything that can not be computed immediately. * * A common use for this structure is to replace the usual Continuation-Passing * Style form of programming, in order to be able to compose and sequence * time-dependent effects using the generic and powerful monadic operations. * * @class * @summary * ((α → Void), (β → Void) → Void)) → Task<E, R> * * Task<E, R> <: Monad[β] * , Functor[β] * , Applicative[β] */ class Task { /** * Represents a lazy asyncronous computation a Task * @constructor * @param {(reject: (error: E) => void, resolve: (value: R) => void) => void} fork - Callback with resolvers for failure and success */ constructor(fork) { this.fork = fork; /** * Chain function * @alias chain */ this.bind = this.chain.bind(this); /** * Chain function * @alias chain */ this.flatMap = this.chain.bind(this); /** * Cata function * @alias fold */ this.cata = this.fold.bind(this); } /** * Pointed constructor of a Task similar to Promise.resolve(any) * @param {R} r - value to resolve * @returns {Task<E, R>} the Task */ static of(r) { return new Task((_, resolve) => resolve(r)); } /** * Returns a rejected Task similar to Promise.reject(any) * @param {E} err - Error to reject * @returns {Task<E, R>} the rejected Task */ static rejected(err) { return new Task(reject => reject(err)); } /** * Transforms a promise to a Task, take into account that this will run the Promise because of its eager nature * @param {Promise<R>} p - Promise to convert to a Task * @returns {Task<E, R>} the Task from the Promise */ static fromPromise(p) { return new Task((reject, resolve) => p.then(resolve).catch(reject)); } /** * Transforms a function that returns a promise to a Task, in this case the promise will be run lazily * @param {() => Promise<R>} p - Promise to convert to a Task * @returns {Task<E, R>} the Task from the Promise */ static fromLazyPromise(lazyPromise) { return new Task((reject, resolve) => lazyPromise().then(resolve).catch(reject)); } /** * Applies natural transformation from Either to Task * @param {Either<E, R>} e - Either kind from monet * @returns {Task<E, R>} the Task from the Either provided */ static fromEither(e) { return new Task((reject, resolve) => e.fold(reject, resolve)); } /** * Transforms a function that throws to Task * @param {() => R} f - Function that returns R and might throw E * @returns {Task<E, R>} the Task */ static fromTry(f) { return new Task((reject, resolve) => { try { return resolve(f()); } catch (error) { return reject(error); } }); } /** * Traverses an array in applicative way transforming every element into a task and running them in parallel * @param {(A) => Task<E, B>} f - Function maps an item to a Task * @param {Array<A>} arr - array of elements to be traversed * @returns {Task<E, Array<B>>} a Task containing an array of transformed elements */ static arrayTraverseA(f, arr) { const cons = (x) => (xs) => xs.concat([x]); return arr.reduce((tl, i) => Task.of(cons).apTo(f(i)).apTo(tl), Task.of([])); } /** * Traverses an array in a monadic way transforming every element into a task and running them in sequence * @param {(A) => Task<E, B>} f - Function maps an item to a Task * @param {Array<A>} arr - array of elements to be traversed * @returns {Task<E, Array<B>>} a Task containing an array of transformed elements */ static arrayTraverseM(f, arr) { return arr.reduce((tl, i) => tl.chain(xs => f(i).chain(x => Task.of(xs.concat(x)))), Task.of([])); } /** * Mimics Promise.all() behaviour but runs sequentially a list of Tasks, fails when some one fails * @param {[Task<E, R>, Task<E1, R1>, ... , Task<EN, RN>]} arr - Array of Tasks to traverse * @returns {Task<E | E2 | ... | EN, [R, R1, ... , RN]>} Task of array of resolved Tasks */ static allSeq(arr) { return arr.reduce((acc, x) => acc.chain((listOfT) => x.map((t) => [...listOfT, t])), Task.of([])); } /** * Mimics Promise.all() behaviour and runs in parallel a list of tasks, fails when some one fails * @param {[Task<E, R>, Task<E1, R1>, ... , Task<EN, RN>]} arr - Array of Tasks to traverse * @returns {Task<E | E2 | ... | EN, [R, R1, ... , RN]>} Task of array of resolved Tasks */ static all(arr) { return arr.reduce((acc, tv) => tv.chain((el) => Task.of((list) => [...list, el])).apTo(acc), Task.of([])); } /** * Mimics Promise.allSeatle() behaviour and runs sequentially a list of Tasks, never fails * @param {[Task<E, R>, Task<E1, R1>, ... , Task<EN, RN>]} arr - Array of Tasks to traverse * @returns {Task<never, [Either<E, R>, Either<E1, R1>, ... , Either<EN, RN>]>} Task of array of resolved Tasks */ static allSeattleSeq(arr) { return arr .map(taskToTaskOfEither) .reduce((acc, x) => x.chain((t) => acc.map(listOfT => [...listOfT, t])), Task.of([])); } /** * Mimics Promise.allSeatle() behaviour and runs in parallel a list of Tasks, never fails * @param {[Task<E, R>, Task<E1, R1>, ... , Task<EN, RN>]} arr - Array of Tasks to traverse * @returns {Task<never, [Either<E, R>, Either<E1, R1>, ... , Either<EN, RN>]>} Task of array of resolved Tasks */ static allSeattle(arr) { return arr .map(taskToTaskOfEither) .reduce((acc, tv) => acc.chain((list) => Task.of((el) => [...list, el])).apTo(tv), Task.of([])); } /** * Adapts a function that returns a Task<Error, Success> to bypass Success and continue composition should be use with chain * @param {((input: S) => Task<E, void>) | ((input: S) => void)} f - Function that returns a Task<E, void> * @param {S} success - Success from the previous function * @returns A Task<Error, Success> of the same output Success */ static tap(f) { return input => { const r = f(input); return r instanceof Task ? r.map(() => input) : Task.of(input); }; } /** * Adapts a function that returns a Task<Error, Success> to bypass error and continue composition should be use with orElse * @param {((input: E) => Task<void, S>) | ((input: E) => void)} f - Function that returns a Task<void, Error> * @param {E} failure - Failure of the previous function * @returns A Task<Error, Success> of the same failure Error */ static rejectTap(f) { return failure => { const r = f(failure); return r instanceof Task ? r.rejectMap(() => failure) : Task.rejected(failure); }; } /** * Applys the successful value of the `Task<E, R>` to the successful * value of the `Task<E, (R → O)>` * @param {Task<E, (r: R) => O>} to - Task holding an `(R → O)` on the success path * @returns {Task<E, O>} the Task with the value applied */ ap(tf) { return new Task((reject, resolve) => { const { guardReject, resolveFn, resolveValue } = synchronize(reject, resolve); this.fork(guardReject, resolveValue); tf.fork(guardReject, resolveFn); }); } /** * Applys the successful value of the `Task<E, (R → O)>` to the successful * value of the `Task<E, R>` * @param {Task<E, O>} to - Task holding an `O` on the success path * @returns {Task<E, O>} the Task with the value applied */ apTo(to) { return new Task((reject, resolve) => { const { guardReject, resolveFn, resolveValue } = synchronize(reject, resolve); to.fork(guardReject, resolveValue); this.fork(guardReject, resolveFn); }); } /** * Applies the function [f] in the success path of resolution of the Task. Functor interface implementation * @param {(r: R) => O} f - Function that transfroms from type R to type O * @returns {Task<E, O>} A Task containing an element of type O */ map(f) { return new Task((reject, resolve) => this.fork((err) => reject(err), (r) => resolve(f(r)))); } /** * Applies the function [g] in the failure path of resolution of the Task * @param {(e: E) => F} g - Function that transfroms from type E to type F * @returns {Task<F, R>} A Task containing an element of type R but a failure of type F */ rejectMap(g) { return new Task((reject, resolve) => this.fork((err) => reject(g(err)), (x) => resolve(x))); } /** * Applies the function [f] in the success path of resolution of the Task. Monad interface implementation * @param {(r: R) => Task<E, O>} f - Function that transfroms from type R to type O * @returns {Task<E, O>} A Task containing an element of type O */ chain(f) { return new Task((reject, resolve) => this.fork((err) => reject(err), (r) => f(r).fork(reject, resolve))); } /** * Applies the function [g] in the failure path of resolution of the Task * @param {(e: E) => Task<F, R>} g - Function that transfroms from type E to type F * @returns {Task<F, R>} A Task containing an element of type R or failure of type F */ orElse(g) { return new Task((reject, resolve) => this.fork((err) => g(err).fork(reject, resolve), (r) => resolve(r))); } /** * Maps over both sides of execution applying [f] over success path or [g] over failure path * @param {(r: R) => Task<E, O>} g - Function that transfroms from type R to type O * @param {(e: E) => Task<F, R>} f - Function that transfroms from type E to type F * @returns A Task containing a failure of type F or a success of type O */ bimap(g, f) { return new Task((reject, resolve) => this.fork((e) => reject(g(e)), (r) => resolve(f(r)))); } /** * Catamorphism. Takes two functions [f] and [g], applies the leftmost one to the failure * value, and the rightmost one to the successful value, depending on which one * is present. * @param {(r: R) => Task<E, O>} f - Function that transfroms from type R to type O * @param {(e: E) => Task<F, R>} g - Function that transfroms from type E to type F * @returns A Task containing a failure of type F or a success of type O */ fold(g, f) { return new Task((_, resolve) => this.fork((e) => resolve(g(e)), (r) => resolve(f(r)))); } /** * Natural transformation to Promise * You don't know when this could come in handy * @returns {Promise<R>} a Promise equivalent */ toPromise() { return new Promise((resolve, reject) => this.fork(reject, resolve)); } } exports.Task = Task; //# sourceMappingURL=task.js.map