将 Fluture 与 Ramda 结合使用
Use Fluture with Ramda
我以前使用 Bluebird 来做异步的事情,但现在必须做很多空/空/错误检查,我不想走通常的 if Else 路线。我正在考虑使用 monad,但还没有完全理解它。
我还希望它能很好地与 ramda 的 pipe / compose
一起使用,因为我的大部分其他代码都巧妙地封装在功能管道中。根据 many discussions, monadic Futures (Fluture 似乎被推荐)比 Promises 更受欢迎,并且在未来的版本中可能会删除对 pipeP 和 composeP 的支持。
Fluture 似乎是一个不错的选择,因为据说它可以与遵循 fantasy-land specs.
的库(如 ramda)配合使用
然而,我完全不知道如何着手实施将 Ramda 的管道与 Fluture 集成的东西。我需要一些示例代码方面的帮助。
例如:
我有一个数据库调用 returns 一个对象数组。该数组可能有值、为空或未定义。我有一个功能管道,可以转换数据并将其 returns 传输到前端。
示例承诺代码:
fancyDBCall1(constraints)
.then(data => {
if (!data || data.length === 0) {
return []
}
return pipe(
...
transformation functions
...
)(data)
})
.then(res.ok)
.catch(res.serverError)
有人可以提供一些关于继续进行的好方法的指示。
不是专家,但由于专家没有回答我想我可以 Maybe
帮助... ;)
我的理解是,您使用 Promise
或 Future
来处理数据流的异步部分,并使用 Maybe
或 Either
来处理 weird/multiple/null
-data.
例如:您可以像这样处理 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)
};
};
你能做什么?
所以,您的代码可以做一些事情。但首先,让我们谈谈 Monad。
在此代码中,您可以使用 3 种类型的 Monad:
- 也许(数据库可能 return 某事,或
nothing
)
- 要么(例如,如果某些数据验证失败)
- Fluture(替换 promise。Flutures 与 promises 不同!)
也许是这样,也许不是!
让我们分解一下您的代码。我们要做的第一件事是确保您的 fancyDBCall1(constraints)
return 是 Maybe
。这意味着它 可能 return 是一个结果,或者什么都不是。
但是,您的 fancyDBCall1
是一个异步操作。这意味着它必须return一个Future
。这里的技巧不是让它 return 成为一个值的未来,就像 Future <Array>
让它成为 return 一个 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) );
看看我们的代码看起来多漂亮?但是等等,还有更多!
我们要么走得更远,要么不走!
所以,如果你仔细观察,你就会知道我漏掉了一些东西。当然,我们现在正在处理 null 检查,但是如果 transform
爆炸了怎么办?嗯,你会说 "We send res.serverError".
好的。这还算公平。但是,如果 transform
函数由于无效的用户名而失败怎么办?
您会说您的服务器崩溃了,但这不完全是真的。您的异步查询很好,但我们得到的数据却不行。这是我们可以预料到的事情,它不像流星击中我们的服务器群,只是一些用户给了我们一个无效的电子邮件,我们需要告诉他!
这里的技巧是改变我们的 transform
函数:
/*
* Accepts <Maybe Array>.
* Returns <Maybe <Either String, Array> >
**/
const tranform = pipe( .... );
哇,天啊香蕉!这是什么黑魔法?
在这里我们说我们的变换可能 return 没有或者可能 return 是一个 Either。这 Either 要么是一个字符串(左分支总是错误)要么是一个值数组(右分支总是正确的结果!)。
综合起来
所以,是的,这是一次非常糟糕的旅行,你说呢?为了给您一些具体的代码供您深入研究,以下是具有这些结构的一些代码可能如下所示:
首先我们来看看 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
] );
到目前为止一切顺利。如果数组符合我们的条件,我们 return 与数组的 Either 的右分支,不是有错误的左分支。
现在,让我们将其添加到我们之前的示例中,这将 return 一个 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}
并干净地退出应用程序。
如果我们的数据库 return 什么都没有,我们打印 []
。
如果数据库做了 return 某事并且某事无效,我们打印 "Invalid Greeting!"
.
如果 DB return 不错,我们打印它!
天哪,香蕉太多了!
嗯,是的。如果您从 Maybe、Either 和 Flutures 开始,您有很多概念需要学习,感到不知所措是正常的。
我个人不知道 Ramda 有什么好的和活跃的 Maybe / Either 库,(也许你可以试试 Folktale 的 Maybe / Result 类型?)这就是我使用 Sanctuary 的原因,它是一个克隆来自 Ramda,它更纯粹并且与 Fluture 很好地集成。
但如果您需要从某个地方开始,您可以随时查看社区 gitter 聊天和 post 问题。阅读文档也有很大帮助。
希望对您有所帮助!
我以前使用 Bluebird 来做异步的事情,但现在必须做很多空/空/错误检查,我不想走通常的 if Else 路线。我正在考虑使用 monad,但还没有完全理解它。
我还希望它能很好地与 ramda 的 pipe / compose
一起使用,因为我的大部分其他代码都巧妙地封装在功能管道中。根据 many discussions, monadic Futures (Fluture 似乎被推荐)比 Promises 更受欢迎,并且在未来的版本中可能会删除对 pipeP 和 composeP 的支持。
Fluture 似乎是一个不错的选择,因为据说它可以与遵循 fantasy-land specs.
的库(如 ramda)配合使用然而,我完全不知道如何着手实施将 Ramda 的管道与 Fluture 集成的东西。我需要一些示例代码方面的帮助。
例如:
我有一个数据库调用 returns 一个对象数组。该数组可能有值、为空或未定义。我有一个功能管道,可以转换数据并将其 returns 传输到前端。
示例承诺代码:
fancyDBCall1(constraints)
.then(data => {
if (!data || data.length === 0) {
return []
}
return pipe(
...
transformation functions
...
)(data)
})
.then(res.ok)
.catch(res.serverError)
有人可以提供一些关于继续进行的好方法的指示。
不是专家,但由于专家没有回答我想我可以 Maybe
帮助... ;)
我的理解是,您使用 Promise
或 Future
来处理数据流的异步部分,并使用 Maybe
或 Either
来处理 weird/multiple/null
-data.
例如:您可以像这样处理 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)
};
};
你能做什么?
所以,您的代码可以做一些事情。但首先,让我们谈谈 Monad。
在此代码中,您可以使用 3 种类型的 Monad:
- 也许(数据库可能 return 某事,或
nothing
) - 要么(例如,如果某些数据验证失败)
- Fluture(替换 promise。Flutures 与 promises 不同!)
也许是这样,也许不是!
让我们分解一下您的代码。我们要做的第一件事是确保您的 fancyDBCall1(constraints)
return 是 Maybe
。这意味着它 可能 return 是一个结果,或者什么都不是。
但是,您的 fancyDBCall1
是一个异步操作。这意味着它必须return一个Future
。这里的技巧不是让它 return 成为一个值的未来,就像 Future <Array>
让它成为 return 一个 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) );
看看我们的代码看起来多漂亮?但是等等,还有更多!
我们要么走得更远,要么不走!
所以,如果你仔细观察,你就会知道我漏掉了一些东西。当然,我们现在正在处理 null 检查,但是如果 transform
爆炸了怎么办?嗯,你会说 "We send res.serverError".
好的。这还算公平。但是,如果 transform
函数由于无效的用户名而失败怎么办?
您会说您的服务器崩溃了,但这不完全是真的。您的异步查询很好,但我们得到的数据却不行。这是我们可以预料到的事情,它不像流星击中我们的服务器群,只是一些用户给了我们一个无效的电子邮件,我们需要告诉他!
这里的技巧是改变我们的 transform
函数:
/*
* Accepts <Maybe Array>.
* Returns <Maybe <Either String, Array> >
**/
const tranform = pipe( .... );
哇,天啊香蕉!这是什么黑魔法?
在这里我们说我们的变换可能 return 没有或者可能 return 是一个 Either。这 Either 要么是一个字符串(左分支总是错误)要么是一个值数组(右分支总是正确的结果!)。
综合起来
所以,是的,这是一次非常糟糕的旅行,你说呢?为了给您一些具体的代码供您深入研究,以下是具有这些结构的一些代码可能如下所示:
首先我们来看看 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
] );
到目前为止一切顺利。如果数组符合我们的条件,我们 return 与数组的 Either 的右分支,不是有错误的左分支。
现在,让我们将其添加到我们之前的示例中,这将 return 一个 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}
并干净地退出应用程序。
如果我们的数据库 return 什么都没有,我们打印 []
。
如果数据库做了 return 某事并且某事无效,我们打印 "Invalid Greeting!"
.
如果 DB return 不错,我们打印它!
天哪,香蕉太多了!
嗯,是的。如果您从 Maybe、Either 和 Flutures 开始,您有很多概念需要学习,感到不知所措是正常的。
我个人不知道 Ramda 有什么好的和活跃的 Maybe / Either 库,(也许你可以试试 Folktale 的 Maybe / Result 类型?)这就是我使用 Sanctuary 的原因,它是一个克隆来自 Ramda,它更纯粹并且与 Fluture 很好地集成。
但如果您需要从某个地方开始,您可以随时查看社区 gitter 聊天和 post 问题。阅读文档也有很大帮助。
希望对您有所帮助!