极光日报
首发于极光日报

JavaScript 中的优雅模式:RORO

简评:RORO 即函数的参数和返回值都为单个对象,这样可以帮助我们简化很多的问题。

接收一个对象,返回一个对象(RORO)

我大部分的函数都只接收一个 object 类型的参数,同时 return 或 resolve 一个 object 类型的返回值。

我发现这是非常的棒,这主要是感谢 ES2015 中引入的 Destructuring 特性,让对象取值变得非常的方便。

对于 destructuring 的介绍可以参考这个视频:

https://www.zhihu.com/video/951448153598251008

我列出几个使用 RORO 的优点:

  • 参数命名
  • 简化默认参数
  • 丰富你的返回值
  • 函数组合更简单

参数命名

假设我们有一个函数用于返回指定角色的用户列表,我们需要传入 role withContactInfoincludeInactive 这三个参数来获取具体的用户。

传统写法:

// 定义 findUsersByRole 函数
function findUsersByRole (
  role, 
  withContactInfo, 
  includeInactive
) {...}

// 函数调用
findUsersByRole(
  'admin',
  true,
  true 
)

调用时可以看到后面的两个参数的含义比较模糊,我们无法知道 true true 具体指的是什么?

我们看看把这个函数改成只接收单个对象会发生什么:

// 定义 findUsersByRole 函数
function findUsersByRole ({
  role,
  withContactInfo, 
  includeInactive
}) {...}

// 调用部分
findUsersByRole({
  role: 'admin', 
  withContactInfo: true, 
  includeInactive: true
})

// 参数顺序不需要关心
findUsersByRole({
  withContactInfo: true, 
  role: 'admin', 
  includeInactive: true
})

// 可以在不影响旧代码的情况下新增参数
findUsersByRole({
  withContactInfo: true, 
  role: 'admin', 
  includeInactive: true,
  newParames: true
})

可以看到函数定义部分和传统的写法几乎没有区别,仅仅是用 {} 将参数括起来。

调用部分可以清晰的看到具体参数功能,并且调用时完全不用关心参数的顺序,只需要关心参数的值。到以后如果需要给函数扩展功能添加参数时也可以不需要破坏老旧代码。

如果需要所有的参数都是可选的,例如:

findUsersByRole() 

为了上面这段代码可以正常调用,我们需要为参数对象设置一个默认值,例如:

function findUsersByRole ({
  role,
  withContactInfo, 
  includeInactive
} = {}) {...}

解构我们传入的参数有一个好处,可以保证参数的不变性。当在函数中修改解构后的参数不会影响传入的原生数据。例如:

const options = {
  role: 'Admin',
  includeInactive: true
}
findUsersByRole(options)
function findUsersByRole ({
  role,
  withContactInfo, 
  includeInactive
} = {}) {
  role = role.toLowerCase()
  console.log(role) // 'admin'
  ...
}
console.log(options.role) // 'Admin' 没有改变

简化默认参数

传统写法:

function findUsersByRole (
  role, 
  withContactInfo = true, 
  includeInactive
) {...}

试想一下如果我们调用 findUsersByRole 函数时只想设置 includeInactive 这个参数为 true,会怎么样:

// 实际调用代码
findUsersByRole(
  'Admin', 
  undefined, 
  true
)

由于参数的顺序问题,我们需要在 withContactInfo 的位置传一个 undefined 。如果使用 RORO 就能够很好的处理这个问题:

function findUsersByRole ({
  role,
  withContactInfo = true, 
  includeInactive
} = {}) {...}

findUsersByRole(
  role: 'Admin',
  includeInactive = true
)

必要参数验证

下面这段代码非常的常见于检验传入的参数:

function findUsersByRole ({
  role, 
  withContactInfo, 
  includeInactive
} = {}) {
  if (role == null) {  
    throw Error(...)
  }
  ...
}
NOTE: == 用于同时检测 null 和 undefined

在每个函数前都加一个检验过于繁琐,我们可以使用默认参数来处理这个问题。

先定义一个检验参数的函数:

function requiredParam (param) {
  const requiredParamError = new Error(
   `Required parameter, "${param}" is missing.`
  )
  // preserve original stack trace
  if (typeof Error.captureStackTrace === ‘function’) {
    Error.captureStackTrace(
      requiredParamError, 
      requiredParam
    )
  }
  throw requiredParamError
}

现在我们在定义参数的时候可以这么写了:

// 定义 findUsersByRole 函数
function findUsersByRole ({
  role = requiredParam('role'),
  withContactInfo, 
  includeInactive
} = {}) {...}


findUsersByRole()  // Error: Required parameter, “role” is missing.

当调用 findUsersByRole 参数时如果没有 role 这个参数会报错。这是一个非常棒的小技巧。

更丰富的返回值

JavaScript 的 function 只能返回一个值,如果这个值是 object 类型那就能够包含更多的信息。

函数组合更加简单

我们使用 pipe 函数将多个函数进行组合,pipe 函数如下:

function pipe(...fns) { 
  return param => fns.reduce(
    (result, fn) => fn(result), 
    param
  )
}

该函数可以传入一组函数,并返回一个函数,该函数可以依次(从左向右)调用函数列表中的函数,并依次将函数的返回结果作为下个函数的参数,这里有个限制是列表中的函数只能接收一个参数,如果使用 RORO 就可以完美解决这个问题。
举个例子,我们已经有一个 saveUer 函数,这个函数可以分成 3 个函数,分别是 validate, normalize 和 persist:

function saveUser(userInfo) {
  return pipe(
    validate,
    normalize,
    persist
  )(userInfo)
}

function validate({
  id,
  firstName,
  lastName,
  email = requiredParam(),
  username = requiredParam(),
  pass = requiredParam(),
  address,
  ...rest
}) {
  // do some validation
  return {
    id,
    firstName,
    lastName,
    email,
    username,
    pass,
    address,
    ...rest
  }
}
function normalize({
  email,
  username,
  ...rest
}) {
  // do some normalizing
  return {
    email,
    username,
    ...rest
  }
}
async function persist({
  upsert = true,
  ...info
}) {
  // save userInfo to the DB
  return {
    operation,
    status,
    saved: info
  }
}
原文:Elegant patterns in modern JavaScript: RORO

极光日报,极光开发者旗下媒体。

每天导读三篇英文技术文章。

编辑于 2018-02-26

文章被以下专栏收录

    简介:每日导读(或翻译)三篇优质英文文章,内容 80% 涉及硅谷/编程/科技/,期待共同成长。