Comments 18
Функциональное программирование неплохо формирует мышление, является очень выразительным и часто лаконичным решением.
return EitherAsync.liftEither(this.stateManager.checkUserExpiration())
.bimap(
// если дата истекла
() => EitherAsync.liftEither(this.stateManager.getUser())
.map((user) => user.id)
.chain((userId) => this.userService.getUser(userId ?? ''))
.map((user) => this.stateManager.setUser(user)
.chain(() => this.stateManager.setUserExpiration())
.reduce((acc) => acc, user))
.chain((user) => EitherAsync.liftEither(this.stateManager.checkUserStatus())
.map(() => user)
.chainLeft(async (statusError) => (await this.authService.removeAllUserSessions(user.id))
.chain(() => this.stateManager.logout())
.map(() => statusError),
),
)
.then((either) => either.extract()),
// если дата НЕ истекла
() => this.stateManager.getUser().extract(),
)
.run()
.then((either) => either.extract());
if( !this.user ) return null
if( !this.userIsExpired ) return this.user
this.user = this.userService.get( user.id ?? '' )
this.userSetExpiration()
if( this.userHaveRightStatus() ) return this.user
this.authService.userSessionsClear( user.id )
this.logout()
return null
Занавес.
Нарушена бизнес логика процесса, пропущена пара важных вызовов, не обработаны ошибки, в конце концов даже возвращается не то, что нужно. А в остальном всё нормально! =)
if( !this.user ) return null
В теории там что-то типа if( !(isPromise(this.user) ? await this.user : this.user) ) return null , и так для каждой строки. Но в практическом смысле таких неопределенностей лучше конечно избегать, если это возможно.
Да нет, именно так как я написал, без монады Promise.
Кажется вам должна понравиться rxjs :)
так и есть =) но руки до нее пока не дошли
Rxjs не монадический, хотя выглядит похоже. Был проект, который добавляет некоторые алгебраические типы поверх https://gcanti.github.io/fp-ts-rxjs/. Монадический аналог для стримов это https://github.com/cujojs/most, откуда rxjs@5 как мне кажется утянули идею с операторами. Но весь хайп уже давно в прошлом.
нет ли на примете полноценного проекта, написанного на rxjs?
Вот пример для души https://blog.thoughtram.io/rxjs/2017/08/24/taming-snakes-with-reactive-streams.html. А так смотрите любое на Angular :)
С rxjs ровно те же проблемы, если чуть больше логики попытаетесь на нем делать, императивный код радикально проще.
метод chain, который принимает на вход метод, возвращающий монаду другого типа (назовем его Monad2).
Не-а. Метод chain (aka flatMap или bind) должен возвращать ту же самую монаду: m a -> (a -> m b) -> m b -- согласно определению монады. В общем случае разные монады между собой не композятся. Собственно в этом главная их проблема.
Условно, если вы положили письмо в конверт, а конверт - в почтовый ящик, то уже не сможете достать письмо обратно из ящика без конверта. Ну или вывернуть наизнанку - чтобы письмо оказалось в ящике, а ящик в конверте.
Для каких-то частных случаев можно придумать такой комбинатор монад, но это если вам повезет. Либо руками создать химеры как EitherAsync.
https://github.com/fantasyland/fantasy-land#fantasy-landchain-method
Если первый пример еще читаемый и делает его читаемым то что массивы имеют схожий функционал, а не использование монад, то второй имхо не читаемый вообще.
Я лично еще не встретил не одного явного плюса в использовании всего этого, кроме пожалуй выучить еще один подход, но для написания чистый функций не обязательно использовать монады(в вашем случае хватит static методов на классах(потому что без typescript это без ошибок не написать) или просто передавать аргументы в функции все зависит от проекта
Монады на JS/TS в дикой жизни