Монады - это довольно полезный способ применения некоторых шаблонов в коде, которые (математически) гарантированно дадут вам определенные результаты.
Я трачу много времени на кодирование на JavaScript (бедняжка!) Для Node.js. За последние несколько лет я обнаружил, что последовательное использование обещаний может оказаться непростым делом. В особенности сейчас парадигма await/async
в ES2017, вероятно, еще больше сбивает с толку вопрос о том, какой стиль программирования принять.
В недавнем проекте мне нужно было убедиться, что асинхронные события выполняются в определенном порядке, и если одно из них выйдет из строя, цепочка перестанет выполняться и сообщит об ошибке. Этот подход полезен, когда, например, бизнес-логика должна быть реализована внутри кода, т.е. требования нетехнические и несколько произвольные. Я написал глубоко уродливую смесь обещаний, возвращающих контейнеры Either. В процессе это подсказывает мне, что Either и Promise работают примерно одинаково.
Вот как «либо» выглядит в 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]); } } }
ВНИМАНИЕ: Хотя приведенный выше код в порядке, желать лучшего с точки зрения статических типов не стоит. Более того, это не единственная возможная реализация, тем более, что внутренняя работа контейнера может быть выполнена иначе.
Самое замечательное в этой монаде то, что вы либо получаете значение или сообщение об ошибке в конце цепочки. Независимо от того, сколько вы сопоставите с «левым» значением, больше ничего не будет выполнено!
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 )
Главный аргумент в пользу слияния Either и Promise заключается в том, что вызов Promise().then
можно рассматривать как эквивалент Either.of().map
. Более того, шаблон
Promise() .then(i => "do something") .catch(error => "something went wrong")
очень похож на
Either.of(...) .either( error => "something went wrong", i => "do something" )
В обоих случаях вы либо получаете значение, либо сообщение об ошибке. Почему бы не жениться на двоих?
Узрите PromisedEither! (работа в процессе)
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); } }
С этим малышом абсолютно законна следующая магия:
Either.Right("Good news") .chain(val => PromisedEither.Right(val)) .map((val, resolve) => resolve(val + '. Excellent!')) .either( () => done(), right => { assert.equal(right, "Good news. Excellent!"); } );
Заключение
Полное раскрытие информации, PromisedEither ЯВЛЯЕТСЯ синтаксическим сахаром в конце дня. Но он применяет определенный шаблон, который будет казаться естественным разработчикам, уже использующим mondas, и дает им структуру, а не пустой холст для заполнения любым кодом ...
Меня зовут Ник Ваклев, я основатель Techccino Ltd | Индивидуальные бизнес-приложения.