javascript es6: use case for destructuring rest parameter
Asked Answered
L

5

8

I just saw a code snippet in MDN about destructuring rest parameters like so:

function f(...[a, b, c]) {
  return a + b + c;
}

f(1)          // NaN (b and c are undefined)
f(1, 2, 3)    // 6
f(1, 2, 3, 4) // 6 (the fourth parameter is not destructured)

the code snippet is in this page: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Functions/rest_parameters

Although the common use case for rest parameters is very clear to me (function foo(...params){/*code*/}) I could not think about a real world use case to use rest parameters like the way presented in that code snippet. Instead, I think that in that case, I should just use a common function definition:

function f(a, b, c) {
  return a + b + c;
}

f(1)          // NaN (b and c are undefined)
f(1, 2, 3)    // 6
f(1, 2, 3, 4) // 6 (the fourth parameter is not defined)
Levin answered 29/12, 2017 at 17:27 Comment(5)
This might be helpful, not sure: gist.github.com/yang-wei/3d35e8692dbc6cc0f98dBerneicebernelle
...[a,,c] however, this is still not really usefulTurtleback
Thank you for the link Amy, but this gist does not talk about that specific scenario :)Levin
I think this is more of a you can rather than you should kind of a deal.. :)Heading
@AndréMarcondesTeixeira yeah i wasn't sure. its okay thoBerneicebernelle
L
2

let's say that we have a function that returns an object such like that:

function getCustomer(id) {
    return fetch(`http://myapi.com/customer/${id}`);
}

and let's say I have a response like that:

{
    "customer": {
        "id": 1234,
        "name": "John Doe",
        "latestBadges": [
            "Platinum Customer",
            "100 Buys",
            "Reviewer"
        ]
    }
}

In a more traditional approach I could write a function to show the latest 3 badges like so:

function showLatestBadges(a, b, c) {
    console.log(a, b, c);
}

and to use that function, I would need to to:

getCustomer(1234).then((customer) => {
    showLatestBadges(
        customer.latestBadges[0],
        customer.latestBadges[1],
        customer.latestBadges[2]
    );
});

With this new spread operator, I could do this instead:

getCustomer(1234).then((customer) => {
    showLatestBadges(...customer.latestBadges);
});

So, using the spread operator in the function definition may look like it's a little useless. But, in fact, it CAN be useful in a VERY specific situation:

Let's say we have a legacy system, and let's say that the call to the showLatestBadges function is being made in hundreds of places without using the spread operator, just like the old days. Let's also assume that we are using a linting tool that prevents unused variables, and let's also assume that we are running a build process that do cares about the linting results, and if the linting says that something is not right, the build fails. Let's ALSO ASSUME that for some weird business rule, we now have to show only the first and third badges. Now, assuming this function call being made in hundreds of places in the legacy system, and we do not have much time available to deliver the implementation of this new business rule, we do not have time to refactor the code for ALL those hundreds of calls. So, we will now change the function as so:

function showLatestBadges(a, b, c) {
    console.log(a, c);
}

But now we have a problem: the build fails because of the unused b variable, and we have to deliver this change for YESTERDAY!!! We have no time to refactor all the hundreds of calls to this function, and we cannot just do a simple find and replace in all the spots, because we have such a messy code, and there are evals all over the place, and unpredictable behavior can happen.

So, one solution is: change the function signature using the spread operator, so the build succeeds, and create a task on the board to do the refactoring. So, we can change the function as so:

function showLatestBadges(...[a,,c]) {
    console.log(a, c);
}

Ok, I know this is a VERY specific situation and that this is very unlike to happen, but, who knows? ¯\_(ツ)_/¯

Levin answered 9/4, 2018 at 14:30 Comment(0)
E
4

Your function f(a, b, c) { … } is indeed the proper way to write this. The only difference between that and the rest+destructuring syntax is that rest parameters do not add to number of parameters, i.e. f.length == 0.

There really is no good use case for putting an array destructuring pattern as the target of a rest parameter. Just because the syntax allows it doesn't mean that it's useful somewhere. The MDN example probably should've made that more clear.

Electromotor answered 29/12, 2017 at 17:49 Comment(0)
L
2

let's say that we have a function that returns an object such like that:

function getCustomer(id) {
    return fetch(`http://myapi.com/customer/${id}`);
}

and let's say I have a response like that:

{
    "customer": {
        "id": 1234,
        "name": "John Doe",
        "latestBadges": [
            "Platinum Customer",
            "100 Buys",
            "Reviewer"
        ]
    }
}

In a more traditional approach I could write a function to show the latest 3 badges like so:

function showLatestBadges(a, b, c) {
    console.log(a, b, c);
}

and to use that function, I would need to to:

getCustomer(1234).then((customer) => {
    showLatestBadges(
        customer.latestBadges[0],
        customer.latestBadges[1],
        customer.latestBadges[2]
    );
});

With this new spread operator, I could do this instead:

getCustomer(1234).then((customer) => {
    showLatestBadges(...customer.latestBadges);
});

So, using the spread operator in the function definition may look like it's a little useless. But, in fact, it CAN be useful in a VERY specific situation:

Let's say we have a legacy system, and let's say that the call to the showLatestBadges function is being made in hundreds of places without using the spread operator, just like the old days. Let's also assume that we are using a linting tool that prevents unused variables, and let's also assume that we are running a build process that do cares about the linting results, and if the linting says that something is not right, the build fails. Let's ALSO ASSUME that for some weird business rule, we now have to show only the first and third badges. Now, assuming this function call being made in hundreds of places in the legacy system, and we do not have much time available to deliver the implementation of this new business rule, we do not have time to refactor the code for ALL those hundreds of calls. So, we will now change the function as so:

function showLatestBadges(a, b, c) {
    console.log(a, c);
}

But now we have a problem: the build fails because of the unused b variable, and we have to deliver this change for YESTERDAY!!! We have no time to refactor all the hundreds of calls to this function, and we cannot just do a simple find and replace in all the spots, because we have such a messy code, and there are evals all over the place, and unpredictable behavior can happen.

So, one solution is: change the function signature using the spread operator, so the build succeeds, and create a task on the board to do the refactoring. So, we can change the function as so:

function showLatestBadges(...[a,,c]) {
    console.log(a, c);
}

Ok, I know this is a VERY specific situation and that this is very unlike to happen, but, who knows? ¯\_(ツ)_/¯

Levin answered 9/4, 2018 at 14:30 Comment(0)
G
1

The example illustrates that rest and destructuring syntaxes are flexible enough to be combined even in such a way.

It is known that neither TypeScript nor Babel stable versions currently support this syntax, primarily because it's of no practical use.

Greenockite answered 12/2, 2018 at 18:14 Comment(0)
H
-1

Actually the ... operator is two ways. It's both called rest and spread depending on your use case. They are both very powerful operators especially for functional approaches. You may always use spread operator as,

var a = [1,2,3],
    b = [4,5,6];
a.push(...b);

which would yield a to be [1,2,3,4,5,6] all at once. At this moment one could say that .concat() could do the same. Yes concat has a built in spread functionality but a.concat(b) wouldn't effect a. I just creates and returns a new array. In fact in proper functional languages treating a as an immutable object is nice for the sake of purity. Yet JS is a weird language. It's believed to be functional but at the same time deeply embraces reference types. So long story short if you want to keep the references to a intact while mutating it then you can not use a.concat(b) but a.push(...b). Here i have to mention that .push() is not perfectly designed because it returns a stupid length property which is totally useless. It should have returned a. So I end up using the comma operator like (a.push(...b),a) most of the times.

OK apart from simple use cases you may stretch ... further for a little more complicated but cool looking implementations. Such as you may do an Haskellesque pattern matching to split head and tail of an array and recurse accordingly.

Here is a useful case of spread and rest operators working hand to hand to flatten an arbitrary nested array.

var flat = (x,...xs) => x ? [...Array.isArray(x) ? flat(...x) : [x], ...flat(...xs)] : [];

var na = [[1,2],[3,[4,5]],[6,7,[[[8],9]]],10];
    fa = flat(na);
console.log(fa);
Haws answered 6/4, 2018 at 23:51 Comment(1)
This doesn't answer the question.Leaved
T
-1

This is one of the use-cases I got to use this

const tail = function([, ...xs]) { return xs; } tail([1,2]); // [2]

const head = ([a]) => a
head([1,2,3,4]) // 1
Tati answered 31/10, 2018 at 5:6 Comment(1)
That's not destructuring the rest parameter.Leaved

© 2022 - 2024 — McMap. All rights reserved.