Hacker News new | past | comments | ask | show | jobs | submit login
The triple dot syntax (` `) in JavaScript: rest vs. spread (2ality.com)
66 points by kiyanwang on May 8, 2022 | hide | past | favorite | 69 comments



Life was hard before these sugars.

Just a couple of years ago I spent a few months with people stuck in the late 2000s, technologically speaking. Their JS was your typical jQuery spaghetti from that time period. Did not have any fun explaining the new stuff, not to mention Promises, to people who were somehow convinced they knew better than me.


Yeah I had an argument on here about callbacks vs. Promises. I’m pretty convinced the ones making the pro callbacks arguments have never actually used JavaScript in a professional capacity, especially in large codebases. I thought the callbacks vs Promises using `async/await` debate was settled.


I’ve never heard anyone argue for callbacks. But the async/await problem isn’t entirely dead since folks still argue generators should be doing they job. They’re similar flavors of cooperative multitasking I guess. But callbacks.. they’re really hard to test without abusing entirely different parts of js. Not that promises are much better.


Yes, but async await isn't really complete without good support for streaming ops and APIs that support them first class. That includes merging streams, canceling them, etc (kinda like Rx). It probably needs both push and pull streams as well. We're not there yet, so in the meantime we have to use events and event listeners, in addition to async/await. I think that's perfectly fine.


Have you tried Ix as a complement to Rx?

https://github.com/ReactiveX/IxJS


No I have not, I prefer to go lightweight so good old event listeners are perfectly fine for my purposes. But once you have ubiquitous first class support for async iterables and so on I'm all aboard. I see Rx very much like an experiment that should influence language features and std libs, rather than a standalone framework.


I couldn't agree more and I've been in this same situation. When your codebase is hundreds of thousands of lines of JavaScript suddenly promises with async await make a lot of sense.


Await is nice, but even with promises, I'd settle with people using them correcly: return something in the promise to the next one can use it without nesting.

But people use promises... and keep nesting.


Even back in early Node days we figured this out with ideas/libs like async.waterfall, which let you turn nesting into a serial flow:

    async.waterfall([function(callback) { callback(null, 'one', 'two'); },
                     function(arg1, arg2, callback) { callback(null, 'three'); },
                     function(arg1, callback) { callback(null, 'done'); }]);
Thus you don't need to make things like 'one' and 'two' into scoped variables that require nesting the second function into the first one so it can see them, and you can split those functions out of being inline lambdas.

Or early Promise libs like coolaj86's futures which included a sequence, letting you do the same sort of thing:

    var seq  = Futures.sequence();
    
    seq
    .then(function(next) { setupContextObj(next, obj)})
    .then(retrieveMD)
    ...
    .then(postUrlDataToS3)
    ...
while also being able to make nice little future() objects you could return from functions and pass around. To be fair it's still JS and lots of third party code needed regular old callbacks so it was still easy to get excessive levels of nesting of various things that leave you with function-closing sequences like });}});return future;} or });}}).end(buf);} that could make a Lisper blush.

These days it's quite a bit nicer having native Promises, a better community-wide understanding of how to use them, but also the syntactic sugar of async/await improves over the waterfall/sequence/.then&.catch chaining styles (especially when it comes to modifications where you want to use data from previous stages of the chain in later stages).


The ResultAsync class in neverthrow has chaining as well as splitting on ok/error - I find this really nice because you can also do away with exceptions at the same time.

I feel like fp-ts is even more powerful for this - provided you (and your entire team / every new hire) can navigate the steep learning curve which tbh defeated me when I tried to learn it in a couple of days last week


I'd argue (from memory!) that there are solid edge cases for nesting promises .. probably all scope-related, where multiple async operations are required for a result that would otherwise require scope-leak / pollution.


> stuck in the late 2000s

jQuery is still the dominant paradigm. It's used on like 80% of websites. Wordpress is also still very widely used


Isn’t jQuery mostly dominant in stats because it’s included in WordPress by default (definitely in 90% of Wordpress themes). It’s been a while since I checked so this could be out of date.


Until very recently it was also a dependency of Bootstrap. That’s why it’s in my project (can’t upgrade yet).


When using classic Angular (some time ago) with Bootstrap I ended up rewriting the JS parts of any bit of Bootstrap we used as Angular components for ease of debugging (I know lots of people hated Angular but it was still easier to only debug one framework-ish thing at once).

The existence today of e.g. react-bootstrap suggests that I'm not the only one to have had this idea.


>I know lots of people hated Angular

Why would they hate on angular? Isn't just another framework


Yes, it's in core. And was only upgraded from a very outdated version fairly recently ( in terms of its lifetime) - https://make.wordpress.org/core/2020/06/29/updating-jquery-v...


I bet bootstrap has something to do with it too.


Many of the bigger libraries like bootstrap but also individual, rich plugins and widgets have moved to a JS native implementation, which is slightly more verbose but clearer, well supported and has no bundle size / dependency overhead.

If I want/need "something more than native" today then I first ask myself again whether I _really_ need it and then maybe I will look for focused/small libraries and polyfills.


Right, but not for new projects.


This SO page describes that in more history and detail: https://stackoverflow.com/questions/44934828/is-it-spread-sy...

But that’s just pedantic if you ask me. A constant source of “me smart” attitude in conversations with those unfamiliar with the concept of grammar. It’s okay to drive a car without being a qualified engine-eer.


For what it’s worth: That’s not what motivated this blog post. It was that I often encounter people who see the three dots being used in parameter definitions or destructuring and don’t understand what is going on (because they think it is spreading). The section on triple dots not being operators is more of a side note.


I don't think GP was saying your blog post was pedantic. I think they were referring to the stack overflow answer (which, unlike yours, was kind of pedantic). Your post was great.

That said, since we're discussing pedantry, I truly do mean for this to be constructive. In the US at least, most people avoid putting the "Dr." title in their name unless they're in a context where it specifically matters (such as on a course syllabus that they're teaching in university, for example). Medical doctors is an area where this is much less true. The "Dr." title can come off to many people as very pedantic unless it is specifically relevant to the situation.

Part of that is probably because historically in software development/engineering there's been a stigma against people that didn't graduate or go to university, so people feel insecure about others with a lot more education than them. It's gotten a lot better, but there's definitely still some of that happening even now.

I also want to say _thank you_ for your books and blog! I've found them quite helpful over the years. Javascript isn't my primary language so I often have to look things up, and I always click on your posts first because I know that they will:

1. Be brief and to the point (not a bunch of extra unrelated stuff to sort through)

2. Be very clear (never adds more confusion)

3. Be technically accurate

4. Thoroughly answer the question in the title

Thanks for all you do kind sir!


Thanks for the kind words!

W.r.t. the “Dr.”: It’s interesting how much reactions differ. In Germany, people tend to think you are smart if you have a title. In the States, people tend to think you’re incapable of functioning in the real world.

I’m still experimenting. The title has worked well for me w.r.t. branding. Sometimes I mention it, sometimes I don’t.


So it seems like the good Doctor does have a doctorate in a very relevant field. Informatics is quite closely related to programming especially as I understand in continental European universities. I find that knowing that the author has studied in the field for 10+ years is very relevant and adds authority to the blog.


The confusion is a byproduct of using the same syntax in multiple places. It's even easy to fall into the trap of reasoning through the logic of what's happening as spreading, e.g.: "If I put the spread syntax right before the argument in my function, I'm telling the engine to essentially spread [ arguments[n] ... arguments[arguments.length] ] into this single argument value, where 'n' is the position of my spread syntax."

It's not truly correct but if you think about it in this manner it actually fits reasonably well into what the result is.


Any good resources to learn more about promises in-depth? Not your usual, cursory overview but in-depth with explanations for complicated usage (like promises returning promises etc.)?


I have written four chapters about asynchronous JavaScript:

– Foundations: https://exploringjs.com/impatient-js/ch_async-js.html

– Promises for async programming: https://exploringjs.com/impatient-js/ch_promises.html

– Async functions: https://exploringjs.com/impatient-js/ch_async-functions.html

– Async iteration: https://exploringjs.com/impatient-js/ch_async-iteration.html


The MDN Docs never disappoint[0]. Even more details can be gotten from the author of OP's link: Exploring JS[1]. And if you're wondering why the catch block isn't typed properly in Typescript, see this [2] thread.

0 - https://developer.mozilla.org/en-US/docs/Web/JavaScript/Refe...

1 - https://exploringjs.com/es6/ch_async.html

2 - https://github.com/microsoft/TypeScript/issues/6283#issuecom...


It's meant for beginners, but I'm very partial to Eloquent JavaScript (I personally think it's one of the best programming books, period. And definitely one of the best JavaScript books)

https://eloquentjavascript.net/11_async.html

Full book: https://eloquentjavascript.net/


"..." in JS is a lot like "*args" in Python, isn't it?


It unifies *args and **kwargs.


I'm not sure I understand the point about kwargs. That's a feature specific to python keyword params - what's the JS equivalent with ... here?


They're probably talking about the fact that it also works on objects (JS equivalent of dicts)


Yes, {**{'hello':1}, **{'world':2}} === {...{hello:1}, ...{world:2}}

The name "kwargs" is unimportant, I'm referring to the ** operator itself.

Edit: worth mentioning this applies in the consuming named arguments sense as well: ({arg1, arg2, ...rest}) => {}


Supersymmetry syntax


except it's not just for function arguments/parameters. It can be used in all sorts of contexts to spread out an array or object. E.g.

```js

  const object1 = {
    field1: true,
    field2: 42
  };

  const object2 = {
    ...object1,
    field3: 'the meaning of life, the universe, and everything'
  };
```


basically. They're both read as "spread"


Shameless plug, I introduced an ECMA proposal for a conditional rest and spread operator a few months ago and would appreciate support or suggestions if you would find it useful.

https://es.discourse.group/t/conditionally-add-elements-to-d...


Not sure about the proposed form, but having proper expression-only ifs in arrays and objects would be nice. Including the spread syntax.

  [
    "foo”,
    if (cond1) bar,
    if (cond2) ...baz,
  ]
Pretty sure it was already proposed and rejected, but still.


This ain’t pretty, but it works:

  [
    “foo”,
    ...(cond1 ? [bar] : []),
    ...(cond2 ? baz : []),
  ]
---

Edit: note to self: read the link before commenting.


Nice one. I run into this regularly, and it always leads to some kind of nullish spread spaghetti. I really like the idea of an empty / non-value operator that works on objects, arguments & iterables.

Q - is it just a matter of liking posts on tc39 discourse to show support?


How efficient is spread? Is the underlying data copied by value? Copied on write? Or copied by reference only?


It's bad and overused.

Example: the top SO answers for "find max of an array" suggest using spread [1]:

    Math.max(...array)
but this runs into implementation-defined limits on the number of arguments you can pass to a function, which is often as low as 65k; now your function is needlessly limited and works differently across browsers.

1: https://stackoverflow.com/questions/1669190/find-the-min-max... and https://stackoverflow.com/questions/45123468/max-of-array-in...


is `.reduce` the preferred method then?


You could use `reduce` but it's a lot slower than a normal loop. And if your array is tiny (such that perf doesn't matter) you can just do the spread operator, so there's no real reason to ever use it.


If you are using the spread operator to copy data to create a new JS object in a performance sensitive loop, it will actually have a big enough overhead that you’ll have performance gains by listing the fields explicitly.

Spreading introduces an overhead at runtime since the object has to be traversed to figure out what it contains before JS will know what to copy over.

Edit: just to be clear, unless you’re looping over a million items or something up there, you should not care :)


I wonder if v8 can optimise that if the object is constructed such that it can infer a 'type' for it (IIRC if v8 can see the list of keys at object construction time it remembers what they are for a bunch of things, no idea if this is one of them.)


In JS there's no such thing as a reference to a primitive, and there's no such thing as an automatic deep-copy of a non-primitive


Copies the value of the reference if pointing to objects, or copies the value directly if primitive.

There is no "pass by reference" in javascript, following the traditional definition, just the same as Java


const foo = [...bar]; is about 20x slower than const foo = bar.slice();

Yes, 20x. It only gets worse the more you do it -- it's both slower and wastes more memory, because it relies on the Iterator protocol, which actually compiles to something like this:

    const foo = [];
    const iter = bar[Symbol.iterator]();
    while (true) {
        const it = iter.next();
        foo.push(it.value);
        if (it.done)
            break;
    }
Minus some extra error handling with try/catch. Note, that all those calls to .next() every loop have to be dispatched; they can be optimized away, but V8 and JSC are very picky about when they can inline something like this. Also note that every call to .next() returns a brand new object, which means a lot of GC pressure. If you're copying an array of 20,000 items, you just created 20,000 tiny objects of garbage which all need to be GC'd.

Last I asked around, everyone said escape analysis would solve this. But in the 7 years I've been writing JS, I've never seen a single engine correctly elide this object creation. Every so often I boot up d8 with its assembly dumper and confirm, that yes, it is still creating a heap object, even in simple cases, yes, even with all optimizations enabled.

I try and ban all uses of the iterator protocol (for...of, spread, some kinds of destructuring assignment) in all the high-performance code I write. It's helpful, but it's a huge battery drain. And it's because they just spec'd the feature wrong.


Why does React use it so much?


The semantics are pass-by-value, but objects are always references, so only the references are copied. That said the JavaScript spec does not require any specific implementation and implementation may chose various optimizations.


Less efficient than classic JS 'arguments' in the case where you would use that, but in most cases it won't be a problem.


rest/spread is a nice bit of syntax worth using in code, but when refactoring be aware that it is not a 1:1 replacement for use of the classic JS 'arguments' builtin. They have different performance characteristics, and in particular a `Function.apply(this, arguments)` and `Function.call(this, ...args)` have different underlying implementations by spec even if they appear to do the same thing, which can mean the latter may be much much slower than you expect, potentially triggering additional garbage collections. I've had to flag this in code review a couple times :(


for high perf code, i've found Object.assign() to be significantly faster than spread *. it also invoke getters, whereas spread does not.

* this statement will self-invalidate in 180 days.


So you’re p<0.05 sure that it’s faster?


Interesting, does the HN system replace three dots (...) with a space in titles? The title of the article contains (`...`), but the title here just has a space.


OP's OS may have replaced three periods (`...`) with an ellipsis character (`…`), which HN probably filters out in titles.


Oh that's interesting. I assumed people copy/pasted titles when they submit!


I wondered that too for a while as well. I then had two people on my team, one on windows and one on macos, and we discovered that macos (or something on the computer) was converting those automatically.

macos (or some application(s) on macos) also seems to use a unicode version of double quotes " instead of the standard ascii one. I spent a ridiculous amount of time debugging a line in a shell script that had the unicode quotes instead of "normal" ones and broke in ways that were horrible to figure out. Eventually I discovered that he had composed the line in Slack and pasted it in. Definitely something to watch out for!


The quote problem has been a long standing issue since before Unicode became the dominant encoding. You will often see older web pages with mojibake caused by MacRoman encoding of smart quotes.


We also used to have fun with MacOS' perldoc command rendering things cleverly and breaking copy-paste out of the documentation - this resulted in the "hilarious" code here: https://metacpan.org/release/HAARG/local-lib-2.000029/source...


I've had the same problem with contributors corrupting JSON.

For reference:

TextEdit - Preferences - Disable 'Smart Quotes' and 'Smart Dashes'


You can do it at the OS level with Preferences -> Keyboard -> Text tab -> Uncheck "Use smart quotes and dashes".


Thank you! Will let them know if this happens again


The article title doesn't have backtick-quotes either, so it certainly looks like they did something manually.


The actual <title> does have backticks. They're in there to make the periods in the on-the-page title display in a <code> tag, it looks like.

I'd put my money on an automatic HN thing, it massages titles a fair amount.


macOS has ellipsis autocorrection, for example. I've had it turned off forever but perhaps it applies to <input type="text"> boxes like in the submission form.




Guidelines | FAQ | Lists | API | Security | Legal | Apply to YC | Contact

Search: