6

我用 Bluebird 做异步的东西,但现在必须做很多空/空/错误检查,我不想走通常的 if Else 路线。我正在考虑使用单子,但还没有完全理解它。

此外,我希望它与 ramda 很好地配合,pipe / compose因为我的大多数其他代码都巧妙地封装在功能管道中。根据许多 讨论, monadic Futures(似乎推荐 Fluture)优于 Promises,并且在未来的版本中可能会删除对 pipeP 和 composeP 的支持。

Fluture 似乎是一个不错的选择,因为它应该与符合幻想世界规范的库(如 ramda)配合得很好。

但是,我完全不知道如何实现将 Ramda 的管道与 Fluture 集成的东西。我需要一些示例代码的帮助。

例如:

我有一个返回对象数组的数据库调用。数组可能有值、为空或未定义。我有一个功能管道,可以转换数据并将其返回到前端。

示例承诺代码:

fancyDBCall1(constraints)
  .then(data => {
    if (!data || data.length === 0) {
      return []
    }
    return pipe(
    ...
    transformation functions
    ...
    )(data)
  })
  .then(res.ok)
  .catch(res.serverError) 

有人可以就继续进行的好方法给出一些指示。

4

2 回答 2

12

你能做什么 ?

所以,有一些事情可以用你的代码做。但首先,让我们谈谈 Monads。

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

  • 也许(数据库可能会返回一些东西,或者nothing
  • 要么(例如,如果某些数据验证失败)
  • Fluture(用来代替Promise。Fluture 与 Promise不同!)

也许是这个,也许不是!

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

哇,耶稣香蕉!这是什么黑魔法?

在这里,我们说我们的变换可能返回 Nothing 或者返回一个 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 ) )
    );

你可以玩弄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}并干净地退出应用程序。

如果我们的数据库没有返回任何内容,我们 print []

如果数据库确实返回了某些东西并且某些东西是无效的,我们打印"Invalid Greeting!".

如果数据库返回一些不错的东西,我们打印它!

耶稣香蕉,这是很多!

嗯,是的。如果您从 Maybe、Either 和 Flutures 开始,您需要学习很多概念,感到不知所措是正常的。

我个人不知道 Ramda 有什么好的和活跃的 Maybe / Either 库,(也许你可以尝试Folktale中的 Maybe / Result 类型?)这就是我使用 Sanctuary 的原因,它是 Ramda 的一个克隆,更纯粹并且集成得很好与 Fluture。

但是,如果您需要从某个地方开始,您可以随时查看社区 gitter 聊天并发布问题。阅读文档也有很大帮助。

希望能帮助到你!

于 2018-07-30T10:51:36.000 回答
4

不是专家,但由于专家没有回答,我想我可以Maybe提供帮助......;)

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

它将记录 aNothing或 aJust(...)包含一个整数。


完整代码示例:npm install ramda(flutureramda-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)
  };
};

于 2017-07-03T14:52:44.257 回答