Используйте Fluture с Рамдой
Я использовал Bluebird для выполнения асинхронных задач, но теперь мне нужно выполнить много пустых / нулевых / ошибок, и я не хочу идти по обычному маршруту Else. Я подумываю об использовании монад, но пока не совсем понял.
Кроме того, я хочу, чтобы он хорошо играл с рамдой pipe / compose
так как большая часть моего другого кода аккуратно заключена в функциональные конвейеры. Согласно многим дискуссиям, монадические фьючерсы (кажется, рекомендуется Fluture) предпочтительнее обещаний, и поддержка pipeP и composeP может быть удалена в будущих версиях.
Fluture кажется хорошим вариантом, поскольку он предположительно хорошо работает с библиотеками (такими как ramda), которые придерживаются спецификаций Fantasy- Land.
Однако я совершенно заблудился относительно того, как реализовать реализацию интеграции трубы Рамды с Fluture. Мне нужна помощь с примером кода.
Например:
У меня есть вызов БД, который возвращает массив объектов. Массив может иметь значения, быть пустым или быть неопределенным. У меня есть функциональный конвейер, который преобразует данные и возвращает их во внешний интерфейс.
Образец кода Promise:
fancyDBCall1(constraints)
.then(data => {
if (!data || data.length === 0) {
return []
}
return pipe(
...
transformation functions
...
)(data)
})
.then(res.ok)
.catch(res.serverError)
Может кто-нибудь дать несколько советов о том, как продолжить.
2 ответа
Не эксперт, но так как эксперты не отвечают, я думал, что смогу Maybe
Помогите...;)
Насколько я понимаю, вы используете Promise
или же Future
обрабатывать асинхронную часть вашего потока данных, и вы используете Maybe
или же Either
обрабатывать странные / множественные / null
-данные.
Например: вы можете сделать свою функцию преобразования данных дескриптором null
вот так:
const lengthDoubled = compose(x => x * 2, length);
const convertDataSafely = pipe(
Maybe,
map(lengthDoubled)
// any other steps
);
Тогда в вашем Future
Вы можете сделать что-то вроде:
Future(/* ... */)
.map(convertDataSafely)
.fork(console.error, console.log);
Который либо войдет Nothing
или Just(...)
содержащий целое число
Полный пример кода: (npm install ramda
, fluture
а также ramda-fantasy
)
const Future = require('fluture');
const Maybe = require('ramda-fantasy').Maybe;
const { length, pipe, compose, map } = require("ramda");
// Some random transformation
// [] -> int -> int
const lengthDoubled = compose(x => x * 2, length);
const convertData = pipe(
Maybe,
map(lengthDoubled)
)
Future(asyncVal(null))
.map(convertData)
.fork(console.error, console.log); // logs Nothing()
Future(asyncVal([]))
.map(convertData)
.fork(console.error, console.log); // logs Just(0)
Future(asyncVal([1,2,3]))
.map(convertData)
.fork(console.error, console.log); // logs Just(6)
Future(asyncError("Something went wrong"))
.map(convertData)
.fork(console.error, console.log); // Error logs "Something went wrong"
// Utils for async data returning
function asyncVal(x) {
return (rej, res) => {
setTimeout(() => res(x), 200);
};
};
function asyncError(msg) {
return (rej, res) => {
setTimeout(() => rej(msg), 200)
};
};
Что ты можешь сделать?
Итак, есть несколько вещей, которые можно сделать с вашим кодом. Но сначала поговорим о монадах.
В этом коде есть 3 типа монад, которые вы можете использовать:
- Возможно (БД может что-то вернуть, или
nothing
) - Либо (например, если проверка некоторых данных не удалась)
- Fluture (заменить обещание. Flutures отличаются от обещаний!)
Может это, а может и нет!
Давайте немного разложим ваш код. Первое, что мы хотим сделать, это убедиться, что ваш fancyDBCall1(constraints)
возвращает Maybe
, Это означает, что он может вернуть результат или ничего.
Тем не менее, ваш fancyDBCall1
это асинхронная операция Это означает, что он должен вернуть Future
, Хитрость в том, что вместо того, чтобы заставить его возвращать будущее значения, например Future <Array>
чтобы вернуть Future < Maybe Array >
,
Вау, это звучит сложно, мистер!
Просто подумайте об этом, как вместо: Future.of('world');
У тебя есть: Future.of( Maybe( 'world' ) );
Не так плохо, верно?
Таким образом, вы избегаете нулевых проверок в вашем коде! Следующие строки исчезнут:
if (!data || data.length === 0) {
return []
}
И ваш пример будет выглядеть примерно так:
/*
* Accepts <Maybe Array>.
* Most ramda.js functions are FL compatible, so this function
* would probably remain unchanged.
**/
const tranform = pipe( .... );
// fancyDBCall1 returns `Future <Maybe Array>`
fancyDBCall1(constraints)
.map( transform )
.fork( always(res.serverError), always(res.ok) );
Видите, как красиво выглядит наш код? Но подождите, это еще не все!
Мы либо идем дальше, либо нет!
Итак, если вы уделяете пристальное внимание, вы знаете, что я что-то упустил. Конечно, сейчас мы обрабатываем нулевую проверку, но что если transform
взрывается? Ну, вы скажете "Мы отправляем res.serverError".
Хорошо. Это честно. Но что если transform
функция не работает из-за неверного имени пользователя, например?
Вы скажете, что ваш сервер взорвался, но это было не совсем так. Ваш асинхронный запрос был в порядке, но данные, которые мы получили, не были. Это то, что мы могли бы ожидать, это не похоже на метеор, поразивший нашу ферму серверов, просто какой-то пользователь дал нам недопустимое электронное письмо, и мы должны сказать ему!
Хитрость здесь будет идти изменить transform
функция:
/*
* Accepts <Maybe Array>.
* Returns <Maybe <Either String, Array> >
**/
const tranform = pipe( .... );
Вау, Иисус бананы! Что это за темная магия?
Здесь мы говорим, что наше преобразование может ничего не возвращать или, возможно, оно возвращает либо. Это либо строка, либо (левая ветвь всегда является ошибкой), либо массив значений (правая ветвь всегда верный результат!).
Собираем все вместе
Так что да, это была настоящая адская поездка, не правда ли? Чтобы дать вам некоторый конкретный код, который вы можете использовать, вот как может выглядеть код с этими конструкциями:
Сначала мы должны пойти с Future <Maybe Array>
:
const { Future } = require("fluture");
const S = require("sanctuary");
const transform = S.map(
S.pipe( [ S.trim, S.toUpper ] )
);
const queryResult = Future.of(
S.Just( [" heello", " world!"] )
);
//const queryResult2 = Future.of( S.Nothing );
const execute =
queryResult
.map( S.map( transform ) )
.fork(
console.error,
res => console.log( S.fromMaybe( [] ) ( res ) )
);
Вы можете поиграть с queryResult
а также queryResult2
, Это должно дать вам хорошее представление о том, что может сделать монада Maybe.
Обратите внимание, что в этом случае я использую Sanctuary, который является пуристской версией Ramda, из-за ее типа Maybe, но вы можете использовать любую библиотеку типов Maybe и быть довольны ею, идея кода будет такой же.
Теперь давайте добавим либо.
Сначала давайте сосредоточимся на нашей функции преобразования, которую я немного изменил:
const validateGreet = array =>
array.includes("HELLO") ?
S.Right( array ) :
S.Left( "Invalid Greeting!" );
// Receives an array, and returns Either <String, Array>
const transform = S.pipe( [
S.map( S.pipe( [ S.trim, S.toUpper ] ) ),
validateGreet
] );
Все идет нормально. Если массив подчиняется нашим условиям, мы возвращаем правую ветвь Either с массивом, а не левая ветвь с ошибкой.
Теперь давайте добавим это к нашему предыдущему примеру, который вернет Future <Maybe <Either <String, Array>>>
,
const { Future } = require("fluture");
const S = require("sanctuary");
const validateGreet = array =>
array.includes("HELLO") ?
S.Right( array ) :
S.Left( "Invalid Greeting!" );
// Receives an array, and returns Either <String, Array>
const transform = S.pipe( [
S.map( S.pipe( [ S.trim, S.toUpper ] ) ),
validateGreet
] );
//Play with me!
const queryResult = Future.of(
S.Just( [" heello", " world!"] )
);
//Play with me!
//const queryResult = Future.of( S.Nothing );
const execute =
queryResult
.map( S.map( transform ) )
.fork(
err => {
console.error(`The end is near!: ${err}`);
process.exit(1);
},
res => {
// fromMaybe: https://sanctuary.js.org/#fromMaybe
const maybeResult = S.fromMaybe( S.Right([]) ) (res);
//https://sanctuary.js.org/#either
S.either( console.error ) ( console.log ) ( maybeResult )
}
);
Итак, что это говорит нам?
Если мы получаем исключение (что-то не ожидалось), мы печатаем The end is near!: ${err}
и мы чисто выйдем из приложения.
Если наша БД ничего не возвращает, мы печатаем []
,
Если БД что-то возвращает и что-то неверно, мы печатаем "Invalid Greeting!"
,
Если БД возвращает что-то приличное, мы печатаем это!
Господи бананы, это много!
Ну, да. Если вы начинаете с Maybe, Either и Flutures, у вас есть много концепций, которые нужно выучить, и это нормально - чувствовать себя подавленным.
Лично я не знаю хорошей и активной библиотеки Maybe / Either для Ramda (возможно, вы можете попробовать типы Maybe / Result из Folktale?), И именно поэтому я использовал Sanctuary, клон от Ramda, который более чистый и хорошо интегрируется. с Fluture.
Но если вам нужно с чего-то начать, вы всегда можете проверить чат сообщества и задать вопросы. Чтение документации также очень помогает.
Надеюсь, поможет!