使用Fluture与Ramda

时间:2017-07-03 06:27:46

标签: javascript monads ramda.js ramda-fantasy fluture

我使用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) 

有人可以就一个好的方法提出一些指示。

2 个答案:

答案 0 :(得分:6)

你能做什么?

因此,在什么时候可以使用您的代码。但首先,让我们谈谈Monads。

在此代码中,可以使用3种类型的Monad:

  • 也许(数据库可能会返回某些信息,或nothing
  • 两者之一(例如,如果某些数据验证失败)
  • Fluture(替换诺言。诺言与诺言不同!)

也许是这样,也许不是!

让我们分解一下代码。我们要做的第一件事是确保您的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 ) )
    );

您可以玩queryResultqueryResult2。这应该使您对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帮助...;)

我理解的方式是,您使用PromiseFuture来处理数据流的异步部分,并使用MaybeEither处理奇怪的/多个/ 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);

将记录包含整数的NothingJust(...)

完整代码示例:(npm install ramdaflutureramda-fantasy

&#13;
&#13;
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;
&#13;
&#13;