Today’s snippet is going to be shorter than usual, because there’s a really easy solution. Many objects have a default method like toString(). You may want to add one of those to your own functions  – provide a default method if no method is specified. In this post I’ll cover one way to do that. In future posts, now that we have v8 in Apps Script,  we’ll look at some more exotic alternatives using Proxy and Reflect.

Motivation

While creating Sheets Workbook functions – converted to Apps Script I noticed that sometimes a function should return an Array version of its result and sometimes not. I wanted to find an easy way of detecting whether the .array() method was present.

Examples

// should return a non-array result (the default)
sumproduct(a1,a2)

// should return an array result
sumproduct(a1,a2).array()
example of default method

A simple example

This example just counts the number of arguments, or stringifies them via 2 specific methods. However, I want the countArgs method to be the default when no method is specified

const tdef = () => {

  // function with a default method
  const dust = (() => {

    // these are the methods
    const methods = {
      countArgs: (...args) => args.length,
      stringArgs: (...args) => JSON.stringify(args)
    }

    // the default method
    const defaultMethod = methods.countArgs

    // add the others as properties
    Object.keys(methods).forEach(key => defaultMethod[key] = methods[key])

    return defaultMethod
  })()

  console.log(dust(1, 2, 3))  //3
  console.log(dust.countArgs(1, 2, 3))  //3
  console.log(dust.stringArgs(1, 2, 3)) //[1,2,3]
}
inline example

Generalized closure

You can see from the above example that there’s an opportunity to generalize this to create a closure version with a default method.

/**
 * makes a closure with a default method
 * @param {object} options arg wrapper
 * @param {object} options.target the original object
 * @param {function} options.defaultMethod the method to add
 * @return {function} the closure with the default method
 */
const makeDust = ({ target, defaultMethod }) => {

  return (() => {

    // add the others as properties
    Object.keys(target).forEach(key => defaultMethod[key] = target[key])

    return defaultMethod
  })()

}
generalized closure

Using  the generalized closure

Here’s the original example, but this time using the generalizer

const mDef = () => {

  const target = {
    countArgs: (...args) => args.length,
    stringArgs: (...args) => JSON.stringify(args)
  }
  const dust = makeDust({ target, defaultMethod: target.countArgs })
  console.log(dust(1, 2, 3)) // 3
  console.log(dust.countArgs(1, 2, 3)) // 3
  console.log(dust.stringArgs(1, 2, 3)) // [1,2,3]
}
using generalized closure

A separate default function

In the examples so far, the default method has been one that already exists in the object, but you can also make a completely separate default method.

const mDef2 = () => {

  const target = {
    countArgs: (...args) => args.length,
    stringArgs: (...args) => JSON.stringify(args)
  }
  const dust = makeDust({ target, defaultMethod: (...args) => Math.min(...args) })
  console.log(dust(1, 2, 3)) // 1
  console.log(dust.countArgs(1, 2, 3)) // 3
  console.log(dust.stringArgs(1, 2, 3)) // [1,2,3]
}
new default method