Umut Benzer

Umut Benzer

Software Engineer, MSc. in Berlin

Ana SayfaKategoriler

A different way of passing parameters in JavaScript via ES6 Object Destructuring

JS Logo JS Logo   One of the annoyances I have about JavaScript is parameter passing. You depend on a parameter order to pass things into a function. Let me give an example to explain what I mean. Following example is created to make a point and not necessarily a good design. 😃

Beginning

Let's say I have a function named recommendPosts. This function will accept some parameters and calculate the best 10 posts of a blog using only the parameters provided.

const best10posts = recommendPosts(allPosts)

Looks simple. It accepts an array of posts and filters the best ones using some algorithm inside.

Things tend to get complicated over time

We decided to show a tailored list of best posts to the user if the user is visiting an individual post's page. To make recommendPosts able to take it into consideration, we need to make current post's id available to it somehow. So we enhanced the function:

const best10posts = recommendPosts(allPosts, currentPostId)

Please note that currentPostId is an optional ID of type string, if provided taken into consideration.

And we add one more: userSegmentId. Again, an optional string ID which represents a specific segment that user belongs to if we know it.

const best10posts = recommendPosts(allPosts, currentPostId, segmentId)

And maybe, we also want to take last comments into consideration while recommending, so we also pass them in. This time it is required.

const best10posts = recommendPosts(allPosts, currentPostId, segmentId, comments)

The randomness of the function parameter order

So, we have four parameters in our hands. allPosts and comments are mandatory, rest is optional. Question is: Who decided the order of parameters that this function accepts? Why currentPostId is before comments?

You shouldn't notice time layers in the code

A probable answer to this could be: "Well, it is all about the time we had implemented. Each developer added one more parameter to the end because it is easier to refactor the rest of the code if you add it to the end."

Believe me, I've been in this situation more than a couple of times and it was never pleasant. In the end, you'd end up with a randomly ordered list of parameters , some of them optional, some of them are not.

There is yet another gotcha...

Please note that both currentPostId and segmentId are optional strings. It is so easy to do a mistake and provide segmentId into currentPostId while implementing or refactoring.

const wrong = recommendPosts(allPosts, segmentId, comments)
const right = recommendPosts(allPosts, null, segmentId, comments)

Typescript might solve this

When carefully prepared, Typescript or other technologies like that might help to prevent some of the issues. If you define custom types (that resolved to a string in the end) for all types, this won't happen:

// recommendPosts(allPosts: Array<Post>, currentPostId: PostId?, segmentId: SegmentId?, comments: Array<Comments>)

const wrong = recommendPosts(allPosts, segmentId, comments)
// Some compiler somewhere will complain that SegmentId type does not match to PostId.

Swift has named parameters

One of the things I like about Swift, the programming language of Apple, is that it allows you to define named variables.

func greetAgain(person: String) -> String {
    return "Hello again, " + person + "!"
}
print(greetAgain(person: "Anna"))

Please see more examples over here. In summary, you give a label to your inputs, they can be defined as optional and their order is not relevant as the only thing that matters is the label name.

Transforming the same concept to JavaScript

Javascript, unfortunately, doesn't offer this out of the box. I am sure that some of the programming languages that compile into JavaScript support this. But… We are talking about pure JavaScript over here. Can we somehow support something similar in pure JavaScript?

Objects come into action

Well, we can use objects. What if we always pass one and only one object to each JavaScript function and treat it as a "data bag"?

Usage:

const best10posts = recommendPosts({allPosts, currentPostId, segmentId, comments})

It looks almost the same. Notice the braces. That is the only difference. Let's check the difference one more time below:

const allPosts = [] // imagine this is an array of posts
const comments = [] // imagine this is an array of comments
const currentPostId = "123"

// before
recommendPosts(allPosts, currentPostId, null, comments)

// after
recommendPosts({allPosts, currentPostId, comments})
// or.. actually all of the following are the same
recommendPosts({currentPostId, comments, allPosts})
recommendPosts({comments, currentPostId, allPosts})
recommendPosts({comments, currentPostId: "123", allPosts})
// and if you want to go extreme, you can always force some eslint-rules to make them always alfabetically ordered via sort-keys rule.

As you see this way of using objects as syntactic sugar has the following advantages:

  1. Order of fields is irrelevant now. In fact, you can even force them to be always alphabetically.
  2. You don't even need to include optional parameters anymore if you don't want to.

How to consume the parameters

Originally, a recommendPosts would be:

function recommendPosts(allPosts, currentPostId, segmentId, comments) {
    // I have access to all \o/
}

Now, it will be:

function recommendPosts({allPosts, currentPostId, segmentId, comments}) {
    // I have access to all \o/
}

How to assign default values

function recommendPosts({allPosts = [], currentPostId = null, segmentId = "default_segment", comments = []} = {}) {
     // I have access to all \o/
}

// following are the same
function recommendPosts()
function recommendPosts({})

// I only assign currentPostId, rest are defaults
function recommendPosts({currentPostId: "123"})

Of course, I picked a nice looking example...

This way of passing parameters is not always useful, actually, sometimes it is cumbersome. For instance, you can't easily create partially applied functions anymore...

Originally, you could do this:

const recommnededPostPartialFn = recommendedPosts.bind(null, allPosts, currentPostId, null)
const best10posts = recommnededPostPartialFn(comments)

AFAIK, there isn't an out-of-box way to create partially applied object keys without manual object merging, thus reinventing Function.bind.

My experiment in Fil

In my open-source project Fil, I tried to follow the convention that I described above. I just wanted to experiment if this makes life easier or harder in the long run. (without taking any kind of performance hits into consideration.)

Conclusion

I found it easier to follow the parameter mapping between caller and callee. It was so easy to revisit and refactor in the future, even without special IDE support.

However, I didn't like the increased syntax due to extra braces. Especially in functions that accept only one parameter, it felt like an overkill. Eventually, I decided NOT to follow this convention for functions that strictly accepts one parameter.

I believe this way of usage of Objects might create confusion amongst developers in a large team. Besides, I am not certain that everyone will see a value of doing this. Although it was a nice experiment and I did see some advantages on the way, this is not something that I am strongly for or against.

What are your thoughts? Leave a comment below!