[译] 编写函数式的 JavaScript 实用指南

栏目: Html5 · 发布时间: 7年前

内容简介:一切皆为函数函数式编程很棒。随着 React 的引入,越来越多的 JavaScript 前端代码正在考虑 FP 原则。但是我们如何在我们编写的日常代码中开始使用 FP 思维模式?我将尝试使用日常代码块并逐步重构它。我们的问题:用户来到我们的登录页面链接后会带一个
[译] 编写函数式的 JavaScript 实用指南

一切皆为函数

函数式编程很棒。随着 React 的引入,越来越多的 JavaScript 前端代码正在考虑 FP 原则。但是我们如何在我们编写的日常代码中开始使用 FP 思维模式?我将尝试使用日常代码块并逐步重构它。

我们的问题:用户来到我们的登录页面链接后会带一个 redirect_to 参数。就像 /login?redirect_to =%2Fmy-page 。请注意,当 %2Fmy-page 被编码为 URL 的一部分时,它实际上是 / my-page 。我们需要提取此参数,并将其存储在本地存储中,以便在完成登录后,可以将用户重定向到 my-page 页面。

第 0 步:必要的方法

如果我们以最简单方式来呈现这个解决方案,我们将如何编写它?我们需要如下几个步骤

  1. 解析链接后参数。
  2. 获取 redirect_to 值。
  3. 解码该值。
  4. 将解码后的值存储在 localStorage 中。

我们还必须将 不安全 的函数放到 try catch 块中。有了这些,我们的代码将如下所示:

function persistRedirectToParam() {
  let parsedQueryParam;
  try {
    //获取连接后的参数{redirect_to:'/my-page'}
    parsedQueryParam = qs.parse(window.location.search); // https://www.npmjs.com/package/qs
  } catch (e) {
    console.log(e);
    return null;
  }
  //获取到参数
  const redirectToParam = parsedQueryParam.redirect_to;
  if (redirectToParam) {
    const decodedPath = decodeURIComponent(redirectToParam);
    try {
      localStorage.setItem('REDIRECT_TO', decodedPath);
    } catch (e) {
      console.log(e);
      return null;
    }
    //返回  my-page
    return decodedPath;
  }
  return null;
}
复制代码

第 1 步:将每一步写为函数

暂时,让我们忘记 try catch 块并尝试将所有内容表达为函数。

// let's declare all of the functions we need to have

const parseQueryParams = query => qs.parse(query);

const getRedirectToParam = parsedQuery => parsedQuery.redirect_to;

const decodeString = string => decodeURIComponent(string);

const storeRedirectToQuery = redirectTo => localStorage.setItem('REDIRECT_TO', redirectTo);

function persistRedirectToParam() {
  // and let's call them

  const parsed = parseQueryParams(window.location.search);

  const redirectTo = getRedirectToParam(parsed);

  const decoded = decodeString(redirectTo);

  storeRedirectToQuery(decoded);

  return decoded;
}
复制代码

当我们开始将所有“结果”用函数的方式表示时,我们会看到我们可以从主函数体中重构的内容。这样处理后,我们的函数变得更容易理解,并且更容易测试。

早些时候,我们将测试主要函数作为一个整体。但是现在,我们有 4 个较小的函数,其中一些只是代理其他函数,因此需要测试的足迹要小得多。

让我们识别这些代理函数,并删除代理,这样我们就可以减少一些代码。

const getRedirectToParam = parsedQuery => parsedQuery.redirect_to;

const storeRedirectToQuery = redirectTo => localStorage.setItem('REDIRECT_TO', redirectTo);

function persistRedirectToParam() {
  const parsed = qs.parse(window.location.search);

  const redirectTo = getRedirectToParam(parsed);

  const decoded = decodeURIComponent(redirectTo);

  storeRedirectToQuery(decoded);

  return decoded;
}
复制代码

第 2 步 尝试编写函数式

好的。现在,似乎 persistRedirectToParam 函数是 4 个其他函数的“组合”让我们看看我们是否可以将此函数编写为合成,从而消除我们存储为 const 的中间结果。

const getRedirectToParam = (parsedQuery) => parsedQuery.redirect_to;

// we have to re-write this a bit to return a result.
const storeRedirectToQuery = (redirectTo) => {
  localStorage.setItem("REDIRECT_TO", redirectTo)
  return redirectTo;
};

function persistRedirectToParam() {
  const decoded = storeRedirectToQuery(
    decodeURIComponent(
      getRedirectToParam(
        qs.parse(window.location.search)
      )
    )
  )

  return decoded;
}
复制代码

这很好。但是我同情读取这个嵌套函数调用的人。如果有办法解开这个混乱,那就太棒了。

第 3 步 更具可读性的组合

如果你已经完成了以上的一些重构,那么你就会遇到 composeCompose 是一个实用函数,它接受多个函数,并返回一个逐个调用底层函数的函数。还有其他很好的资源来学习 composition ,所以我不会在这里详细介绍。

使用 compose ,我们的代码将如下所示:

const compose = require('lodash/fp/compose');
const qs = require('qs');

const getRedirectToParam = parsedQuery => parsedQuery.redirect_to;

const storeRedirectToQuery = redirectTo => {
  localStorage.setItem('REDIRECT_TO', redirectTo);
  return redirectTo;
};

function persistRedirectToParam() {
  const op = compose(
    storeRedirectToQuery,
    decodeURIComponent,
    getRedirectToParam,
    qs.parse
  );

  return op(window.location.search);
}
复制代码

compose 内的函数执行顺序为从右向左,即最右边的函数(最后一个参数)最先执行,执行完的结果作为参数传递给前一个函数。因此,在 compose 链中调用的第一个函数是最后一个函数。

如果你是一名数学家并且熟悉这个概念,这对你来说不是一个问题,所以你自然会从右到左阅读。但对于熟悉命令式代码的其他人来说,我们想从左到右阅读。

第 4 步 pipe(管道)和扁平化

幸运的是这里有 pipe(管道)compose 做了同样的事情,但是执行顺序和 compose 是相反的,因此链中的第一个函数最先执行,执行完的结果作为参数传递给下一个函数。

而且,似乎我们的 persistRedirectToParams 函数已经成为另一个我们称之为 op 的函数的包装器。换句话说,它所做的只是执行 op 。我们可以摆脱包装并“扁平化”我们的函数。

const pipe = require('lodash/fp/pipe');
const qs = require('qs');

const getRedirectToParam = parsedQuery => parsedQuery.redirect_to;

const storeRedirectToQuery = redirectTo => {
  localStorage.setItem('REDIRECT_TO', redirectTo);
  return redirectTo;
};

const persistRedirectToParam = fp.pipe(
  qs.parse,
  getRedirectToParam,
  decodeURIComponent,
  storeRedirectToQuery
);
复制代码

差不多了。请记住,我们适当地将 try-catch 块留在后面,以使其达到正确的状态?好的接下来,我们需要一些方式来介绍它。qs.parse 和 storeRedirectToQuery 都是不安全。一种选择是使它们成为包装函数并将它们放在 try-catch 块中。另一种 函数式方式 是将 try-catch 表示为一种函数。

第 5 步 作为函数的异常处理

有一些实用程序做到了这一点,但让我们自己尝试写一些东西。

function tryCatch(opts) {
  return args => {
    try {
      return opts.tryer(args);
    } catch (e) {
      return opts.catcher(args, e);
    }
  };
}
复制代码

我们的函数在这里需要一个包含 tryer 和 catcher 函数的 opts 对象。它将返回一个函数,当使用参数调用时,使用所述参数调用 tryer 并在失败时调用 catcher。现在,当我们有不安全的操作时,我们可以将它们放入 tryer 部分,如果它们失败,则从捕获器部分进行救援并提供安全结果(甚至记录错误)。

第 6 步 把所有东西放在一起

因此,考虑到这一点,我们的最终代码如下:

const pipe = require('lodash/fp/pipe');
const qs = require('qs');

const getRedirectToParam = parsedQuery => parsedQuery.redirect_to;

const storeRedirectToQuery = redirectTo => {
  localStorage.setItem('REDIRECT_TO', redirectTo);
  return redirectTo;
};

const persistRedirectToParam = fp.pipe(
  tryCatch({
    tryer: qs.parse,
    catcher: () => {
      return {
        redirect_to: null // we should always give back a consistent result to the subsequent function
      };
    }
  }),
  getRedirectToParam,
  decodeURIComponent,
  tryCatch({
    tryer: storeRedirectToQuery,
    catcher: () => null // if localstorage fails, we get null back
  })
);

// to invoke, persistRedirectToParam(window.location.search);
复制代码

这或多或少是我们想要的。但是为了确保代码的可读性和可测试性得到改善,我们也可以将“安全”函数(tryCatch 函数)分解出来。

const pipe = require('lodash/fp/pipe');
const qs = require('qs');

const getRedirectToParam = parsedQuery => parsedQuery.redirect_to;

const storeRedirectToQuery = redirectTo => {
  localStorage.setItem('REDIRECT_TO', redirectTo);
  return redirectTo;
};

const safeParse = tryCatch({
  tryer: qs.parse,
  catcher: () => {
    return {
      redirect_to: null // we should always give back a consistent result to the subsequent function
    };
  }
});

const safeStore = tryCatch({
  tryer: storeRedirectToQuery,
  catcher: () => null // if localstorage fails, we get null back
});

const persistRedirectToParam = fp.pipe(
  safeParse,
  getRedirectToParam,
  decodeURIComponent,
  safeStore
);
复制代码

现在,我们得到的是一个更强大功能的函数,由 4 个独立的函数组成,这些函数具有高度内聚性,松散耦合,可以独立测试,可以独立重用,考虑异常场景,并且具有高度声明性。

有一些 FP 语法糖使这变得更好,但是这是以后的某一天。

如果发现译文存在错误或其他需要改进的地方请指出。


以上所述就是小编给大家介绍的《[译] 编写函数式的 JavaScript 实用指南》,希望对大家有所帮助,如果大家有任何疑问请给我留言,小编会及时回复大家的。在此也非常感谢大家对 码农网 的支持!

查看所有标签

本站部分资源来源于网络,本站转载出于传递更多信息之目的,版权归原作者或者来源机构所有,如转载稿涉及版权问题,请联系我们

The Linux Command Line

The Linux Command Line

William E. Shotts Jr. / No Starch Press, Incorporated / 2012-1-17 / USD 39.95

You've experienced the shiny, point-and-click surface of your Linux computer-now dive below and explore its depths with the power of the command line. The Linux Command Line takes you from your very ......一起来看看 《The Linux Command Line》 这本书的介绍吧!

MD5 加密
MD5 加密

MD5 加密工具

XML 在线格式化
XML 在线格式化

在线 XML 格式化压缩工具

HEX HSV 转换工具
HEX HSV 转换工具

HEX HSV 互换工具