简体   繁体   English

Ramda JS - 将可选参数传递给 pipe 的第二个 function

[英]Ramda JS - Pass optional argument to the second function of a pipe

I am trying to refactor this code:我正在尝试重构此代码:

async function getUserDataByUsername(username, cached = true) {
  const usernameRef = firestore
    .collection("usernames")
    .doc(username.toLowerCase());

  const usernameDoc = await usernameRef.get();

  if (!usernameDoc.exists) {
    throw userErrors.userNotFound();
  }

  const { userId } = usernameDoc.data();

  return memoizedGetUserData(userId, cached);
}

For that, I have thought to split it in smaller parts, as follows:为此,我曾考虑将其拆分为更小的部分,如下所示:

function memoizedGetUserData(userId, cached = true) { 
  ... Fetching from LRU or DB ... 
}

async function getUserId(username) {
  const usernameRef = firestore
    .collection("usernames")
    .doc(username.toLowerCase());

  const usernameDoc = await usernameRef.get();

  if (!usernameDoc.exists) {
    throw userErrors.userNotFound();
  }

  const { userId } = usernameDoc.data();

  return userId;
}

async function getUserDataByUsername(username, cached = true) {
  const userId = await getUserId(username);

  return memoizedGetUserData(userId, cached);
}

Now, I want to apply Ramda to this module.现在,我想将 Ramda 应用到这个模块。 I have never used this library before, but I have read that it is really cool, and makes the code easier to understand with some of its utilities.我以前从未使用过这个库,但我读到它真的很酷,并且通过它的一些实用程序使代码更容易理解。

What I am trying is to refactor the original method using the pipeline style, as follows:我正在尝试使用管道样式重构原始方法,如下所示:

import R from "ramda";

...

const getUserDataByUsername = R.pipeP(getUserId, memoizedGetUserData);

But... how can I pass the second optional parameter "cached", only to the second argument of my pipe??但是......我怎样才能将第二个可选参数“缓存”传递给我的 pipe 的第二个参数?

 const R = require("ramda") function memoizedGetUserData(data) { const [userId, cached = true] = data console.log(userId,cached); // hello false } // async function getUserId(...data) { const [username,cached] = data const userId = await Promise.resolve(username) return [userId,cached]; } // memoizedGetUserData arguments must be unary const getUserDataByUsername = R.pipeP(getUserId,memoizedGetUserData) getUserDataByUsername('hello',false)

I think we need to start with this:我认为我们需要从这个开始:

Now, I want to apply Ramda to this module.现在,我想将 Ramda 应用到这个模块。 I have never used this library before, but I have read that it is really cool, and makes the code easier to understand with some of its utilities.我以前从未使用过这个库,但我读到它真的很酷,并且通过它的一些实用程序使代码更容易理解。

I'm afraid that is putting things in the wrong order.恐怕这是把事情弄错了。 Ramda (disclaimer: I'm one of its authors) is designed for a simple purpose: making it simpler to write in an Functional Programming (FP) manner. Ramda(免责声明:我是它的作者之一)的设计目的很简单:以函数式编程 (FP) 方式编写更简单。 It is not a general-purpose library in the mold of Underscore or lodash.它不是 Underscore 或 lodash 模具中的通用库。 While it can make your code easier to understand (and I for one do think it's cool), if you're not looking to write in FP style, then adding Ramda will likely be counterproductive.虽然它可以让你的代码更容易理解(我确实认为它很酷),但如果你不想用 FP 风格编写,那么添加 Ramda 可能会适得其反。

With that out of the way, let's assume you do want to start moving toward FP, and Ramda will be the first step on that journey.有了这些,让我们假设您确实想开始向 FP 迈进,而 Ramda 将是该旅程的第一步。 Then lets look at the code.然后让我们看一下代码。 The main function is something like主要的 function 是这样的

const getUserDataByUsername (username, cached = true) => { /* ... */ }

Ramda is very invested in curried functions that are easy to compose into pipelines. Ramda 在易于组合到管道中的柯里化函数上投入了大量精力。 That means that optional arguments are almost impossible to deal with well.这意味着可选的 arguments 几乎不可能很好地处理。 Usually in Ramda, when we work with defaulted optional arguments, we create one function that requires these values, then build another one atop it that partially applies the default values.通常在 Ramda 中,当我们使用默认的可选 arguments 时,我们会创建一个需要这些值的 function,然后在其上构建另一个部分应用默认值的 function。 It might look like this:它可能看起来像这样:

const userByName = (cached) => (username) => { /* ... */ }
const getUserDataByUsername = userByName (true)

With Ramda's currying, we might also write that as有了 Ramda 的柯里化,我们也可以这样写

const userByName = curry ((cached, username) => { /* ... */ })

We now have two functions, userByName is the more generic, but it requires you to supply the cached variable.我们现在有两个函数, userByName更通用,但它需要您提供cached的变量。 getUserDataByUsername is simpler, requiring only the username . getUserDataByUsername更简单,只需要username

In order to do this, though, we will also need to change memoizedGetUserData , which has a similar (userId, cached = true) => { /*... */ } stucture.但是,为了做到这一点,我们还需要更改memoizedGetUserData ,它具有类似的(userId, cached = true) => { /*... */ }结构。 Again we could manually curry it like this:同样我们可以像这样手动咖喱它:

const memoizedGetUserData = (cached) => (userId) => { /* ... */ }

or use Ramda's currying:或使用 Ramda 的柯里化:

const memoizedGetUserData = curry ((cached, userId) => { /* ... */ })

Note that in both these cases, we have moved what was a defaulted optional parameter from the end to the beginning of the signature.请注意,在这两种情况下,我们都将默认的可选参数从签名的末尾移到了开头。 Ramda's design around currying and partial application means that we always order the parameters from least likely to change to most likely to change. Ramda 围绕柯里化和偏应用的设计意味着我们总是将参数从最不可能改变到最有可能改变的顺序排列。 If we're defaulting a parameter, we clearly believe it's less likely to change, so it should appear earlier.如果我们默认一个参数,我们显然相信它不太可能改变,所以它应该更早出现。 Some people describe this as "data-last", but I think it's a bit more subtle than that.有些人将其描述为“数据最后”,但我认为它比这更微妙。

Let's now implement that main function.现在让我们实现那个主要的 function。 The original looked like this:原来的样子是这样的:

async function getUserDataByUsername(username, cached = true) {
  const userId = await getUserId(username);

  return memoizedGetUserData(userId, cached);
}

Here's how I might first look to refactor it:以下是我可能首先考虑重构它的方式:

const userByName = (cached) => (username) => getUserId (username) .then (memoizedGetUserData (cached))

That might be fine on its own.这本身可能没问题。 There's only two steps.只有两个步骤。 A pipeline might be unnecessary, but for practice, let's look at how we might write a pipeline for this.管道可能是不必要的,但为了练习,让我们看看我们如何为此编写管道。 First off, note that pipeP and composeP , the specific Promise pipelining functions, have been deprecated for some time in favor of the more generic pipeWith / composeWith functions paired with andThen , and in fact, they have been removed entirely from the just-released version .首先,请注意pipePcomposeP ,特定的 Promise 流水线函数,已经被弃用了一段时间,取而代之的是更通用的pipeWith / composeWith函数与andThen配对,事实上,它们已从刚刚发布的版本中完全删除.

So, using pipeWith it could look like this:因此,使用pipeWith它可能看起来像这样:

const userByName = (cached) => pipeWith (andThen, [getUserId, memoizedGetUserData (cached)])

This should behave exactly the same as the above.这应该与上面的行为完全相同。

I personally would probably stop here.我个人可能会停在这里。 But many people like their functional programs to be mostly point-free.但许多人喜欢他们的功能程序大多是无点的。 I'm not sure there's any great reason here, but if we wanted to do it, we could go one step further, and write this as我不确定这里有什么很好的理由,但是如果我们想这样做,我们可以 go 更进一步,并将其写为

const userByName = pipe (memoizedGetUserData, andThen, flip (o) (getUserId))

I don't find this version very compelling, and would choose one of the first two, but any of them should work.我觉得这个版本不是很有说服力,并且会选择前两个中的一个,但它们中的任何一个都应该可以工作。

You can see this in action, with dummy versions of some of your dependencies by expanding this snippet:您可以通过扩展此代码段来查看实际情况,其中包含一些依赖项的虚拟版本:

 // dummy const memoizedGetUserData = curry ((cached, userId) => ({id: 123, first: 'Fred', last: 'Flintstone'})) async function getUserId(username) { const usernameRef = firestore.collection("usernames").doc(username.toLowerCase()); const usernameDoc = await usernameRef.get(); if (.usernameDoc.exists) { throw userErrors;userNotFound(). } const { userId } = usernameDoc;data(); return userId, } const userByName = (cached) => pipeWith (andThen, [getUserId. memoizedGetUserData (cached)]) // or // const userByName = (cached) => (username) => getUserId (username),then (memoizedGetUserData (cached)) // or // const userByName = compose (flip (o) (getUserId), andThen. memoizedGetUserData) const getUserDataByUsername = userByName (true) getUserDataByUsername ('fred').then (console.log).catch (console .warn)
 <script src="//cdnjs.cloudflare.com/ajax/libs/ramda/0.28.0/ramda.min.js"></script> <script> const {curry, andThen, pipeWith} = R // Dummy versions const firestore = {collection: (name) => ({doc: () => ({get: () => Promise.resolve ({exists: () => true, data: () => ({id: 123, first: 'Fred', last: 'Flintstone'})})})})} const userErrors = {userNotFound: () => 'oops'} </script>

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM