0

I want to know if an argument will throw if I use it in a key-value spread construction:

{ ...maybeIWillThrow }

I've got a bunch of functions that use spread to concisely implement "default options" behavior, like so:

const DEFAULTS = { bells: true, whistles: true }

function doThing( arg1, options ) {
  let effectiveOptions = { ...DEFAULTS, ...options }
  // do work...
}

Now I want to go back and add a few guard clauses to protect against calling with a non-hash options argument. Using try/catch feels clunky because it requires two code blocks instead of one, and my preference would be a ternary or maybe a single-statement detour:

let effectiveOptions = spreadsLikeButter(options)
  ? { ...DEFAULTS, ...options }
  : { ...DEFAULTS }

MDN suggests that testing for iterability is not right here (emphasis added):

Spread syntax (other than in the case of spread properties) can be applied only to iterable objects

I know I can protect against undefined with default parameters, but that won't help if callers pass 5 or true, and I assume typeof options === 'object' will produce too many false positives (e.g. Date, Promise).

Is there a concise way to test a variable to see whether it will spread into an object without throwing?

Right now I'm wrapping try/catch in a willItBlend( value: any ): boolean, but I'm hoping to replace that with something cleaner once I understand this better.

Also, I know I could move all the merge-with-defaults logic into a separate function that can hide the mess -- and maybe that's a good idea in my case -- but for academic purposes let's focus specifically on how to test a variable to determine wether it will spread.

Tom
  • 8,509
  • 7
  • 49
  • 78
  • 2
    You *are* spreading properties. So there is no need to check if it's iterable (it'd be wrong anyway), just check if it's an object. – VLAZ Apr 05 '21 at 16:58
  • @VLAZ - Arrays are Objects - I presume OP needs to ensure it has the right shape too, not just any object? – Randy Casburn Apr 05 '21 at 17:02
  • 1
    "_Is there a concise way to test a variable to see whether it will spread into an object without throwing?_" - this: `if(Object.keys(options).length)` will ensure it is a hash and/or if it is empty. – Randy Casburn Apr 05 '21 at 17:06
  • 1
    @RandyCasburn yes, they are. And they are also spreadable as properties, if you wish to do that with them. The question is for "without throwing" - spreading an array into an object will not do that. And spreading objects of different shapes won't, either. TBH, now that I think of it, almost nothing will throw normally, unless you maybe have getters/setters. I'm not sure what the question is asking any more, as primitives are valid and wouldn't throw. – VLAZ Apr 05 '21 at 17:08
  • You're right. Quick experimentation shows that the garden variety stuff fails silently, contributing nothing to the output and without disrupting execution. Perhaps no guards are needed. – Tom Apr 05 '21 at 17:12
  • 1
    [What is the problem?](https://jsbin.com/fabaqix/edit?js,console) What values throw for you? – VLAZ Apr 05 '21 at 17:13
  • @VLAZ I didn't actually have a problem. Am writing unit tests and SOP suggests that I ought to verify that these functions throw if callers pass garbage. Instead, I may just assert that passing garbage results in using defaults across the board. If I want to actually help consumers discover that they're passing garbage, I'll move all this into a dedicated function that validates the object in depth. – Tom Apr 05 '21 at 17:17
  • @Tom - I. suppose I'm reading into the question a bit, but it seems your addressing "a hash" drove me to the comments about arrays. Sure, arrays will spread like anything else. But, are they usable in your scenario? I doubt it. It would seem to me you want ensure you have a hash as your options object - throwing is not relevant (so I blew that part off). What is the problem? – Randy Casburn Apr 05 '21 at 17:18
  • You *are* using TS, so I'm not sure why you even need run-time checks. Just make sure `options` is *at least* of type `object` or better yet - the correct interface. And then you'd get compile-time check for it. EDIT: wait, I don't know why I thought you used TS. Sorry, I'm bad at reading, I guess. – VLAZ Apr 05 '21 at 17:20
  • then, you'll need to validate and throw yourself. – Randy Casburn Apr 05 '21 at 17:20
  • @Tom Please define "*garbage*" more precisely. `null` and `undefined` spread totally fine for example. Other cases you've mentioned simply are treated as empty objects. Do you just want to recognise primitive values? – Bergi Apr 05 '21 at 17:56

0 Answers0