我使用Bluebird做异步操作,但是现在必须做很多空/空/错误检查,而且我不想按照惯例去Else路线。我正在考虑使用monad,但尚未彻底完成它。
此外,我希望它与ramda的pipe / compose
很好地配合,因为我的大多数其他代码都整齐地封装在功能管道中。根据{{3}} many,monadic Futures(似乎推荐discussions)比Promises更受欢迎,对pipeP的支持和composeP可能会在将来的版本中删除。
Fluture似乎是一个不错的选择,因为它可以很好地与符合Fluture的库(如ramda)配合使用。
然而,我完全迷失了如何实现将Ramda的管道与Fluture集成在一起的东西。我需要一些示例代码的帮助。
例如:
我有一个DB调用,它返回一个Object数组。数组可能具有值,为空或未定义。我有一个功能管道,可以转换数据并将其返回到前端。
示例承诺代码:
fancyDBCall1(constraints)
.then(data => {
if (!data || data.length === 0) {
return []
}
return pipe(
...
transformation functions
...
)(data)
})
.then(res.ok)
.catch(res.serverError)
有人可以就一个好的方法提出一些指示。
答案 0 :(得分:6)
因此,在什么时候可以使用您的代码。但首先,让我们谈谈Monads。
在此代码中,可以使用3种类型的Monad:
nothing
)让我们分解一下代码。我们要做的第一件事是确保您的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 monad可以做什么有所了解。
请注意,在这种情况下,我使用的是Sanctuary,它是Ramda的纯粹版本,因为它可能是Maybe类型,但是您可以使用任何Maybe类型库并对其满意,代码的想法会一样。
现在,让我们添加Either。
首先让我们关注我们的转换函数,我对其做了一些修改:
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}
,然后干净地退出该应用程序。
如果我们的数据库未返回任何内容,我们将打印[]
。
如果DB确实返回了某些内容并且该内容无效,那么我们将打印"Invalid Greeting!"
。
如果数据库返回了不错的东西,我们将其打印出来!
是的,是的。如果您从Maybe,Either和Flutures入手,则有很多概念需要学习,并且感到不知所措是正常的。
我个人不知道Ramda的任何有效的Maybe / Either库,(也许您可以尝试Folktale中的Maybe / Result类型?),这就是为什么我使用了Sanctuary(Ramda的克隆)的原因更加纯净,并且可以与Fluture完美集成。
但是,如果您需要从某个地方开始,则可以随时查看社区问题聊天并发布问题。阅读文档也有很多帮助。
希望有帮助!
答案 1 :(得分:4)
不是专家,但由于专家们没有回答,我以为我可以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)
};
};
&#13;