getify / You-Dont-Know-JS

A book series on JavaScript. @YDKJS on twitter.
Other
179.71k stars 33.51k forks source link

"async & performance" - ljharb #248

Closed ljharb closed 9 years ago

ljharb commented 9 years ago

Preface:

Chapter 1:

Chapter 2:

Chapter 3:

Chapter 4:

mallowigi commented 9 years ago

+1

getify commented 9 years ago

your ducktyping thenable code could be much simplified...which also would cover a primitive...

I don't think so.

From Promises/A+ 1.1:

“thenable” is an object or function that defines a then method.

Furthermore, ES6 Draft 25.4.1.4.2 says:

  1. If Type(resolution) is not Object, then Return FulfillPromise(promise, resolution).

In other words, a thenable has to already be an object or function, and thus primitives are not boxed to be their equivalent object counterparts for the purposes of that check. The value 42 by itself would/should never be interpreted as a thenable object but just a value for a promise to wrap around. You'd have to forcibly coerce/box 42 to its Number object wrapper equivalent to get it to be treated as a thenable.

As such, your version of the test would inaccurately treat the value 42 with a Number.prototype.then present as a thenable, when the specified behavior is to assume its just a primitive value 42 and thus should not be thenable-unwrapped.

Verifying in Chrome:

screen shot 2014-11-23 at 3 16 59 pm

getify commented 9 years ago

Is it worth noting that the HTML5 spec means that setTimeout can't possibly schedule any sooner than 4ms, and prior to HTML5 it was like 20ms?

Technically, that's not quite accurate. Clamping for setTimeout(..) is supposed to only occur for recursive calls, although that point is in debate. Ostensibly, the first call to setTimeout(..) is not supposed to be clamped, though observably it is in some cases (see below).

But then again, even this is not consistent across browsers nor versions within the same browser. The HTML5 spec only said the clamping (if any) can't be more than 4ms. Prior, it was 16ms on windows platforms. Some browsers like Chrome have experimented with shorter clamping like 1ms or 2ms.

Check this out:

screen shot 2014-11-23 at 3 55 21 pm

I think there's far too little consistency here to make it worth mentioning. It will just confuse people and send them down an unsatisfying rabbit hole.

getify commented 9 years ago

the emphasis on "completion"/"continuation" seems strange; i've never really heard them used before

This whole section is another metaphor for what promises are like, much like the one before it about the fast food order receipt metaphor. "completion" and "continuation" are not meant to be terms labeling promises, they're meant to be a custom scenario you could make for yourself, which in doing would be a metaphor for how promises work.

I'll try to see if I can strengthen the explanation of it being a metaphor and not an actual terminology illustration.

getify commented 9 years ago

i'm not on board with the error throwing stuff you believe

Just curious, could you be more specific on what you're referring to that you disagree with?

getify commented 9 years ago

I'm really not a fan of the casual way your code examples modify globals.

TBH, I've gone back and forth on this a number of times. Is a proper polyfill pattern of:

if (!Promise.map) {
   Promise.map = ..
}

really that much worse than:

if (!myHappyFunPromiseMap) {
   myHappyFunPromiseMap = ..
}

Obviously, the more generic the name, the more likely to have future collision. So in that respect, myHappyFunPromiseMap(..) wins out.

But on the other hand, there's already tons of promise libraries that are not only extending the Promise.* API space but also adding extra methods to promise instances. This water is completely poisoned already. It's going to be really messy in ES7 if they try to start adding to it, because of so many "polyfill" libraries (which are WAAAY more than just polyfills) extending these globals.

There's also the balance that in a book, myHappyFunPromiseMap(..) sounds both funny and quite unique, but that's never what any reasonable developer would call such a utility in their real code. They'd at best call it promiseMap(..).

I had promiseMap(..) in a bunch of these examples, then I just decided to go change them all to Promise.map(..).

Your point about adding a note of caution is duly warranted. That's why I was careful to add the if (!..){ .. } checks around any such place. But ultimately, I think either side of the fence is already screwed as it relates to the future, so I'm not convinced teaching with Promise.map(..) is that much worse than teaching with a global promiseMap(..) or a global myHappyFunPromiseMap(..).

getify commented 9 years ago

That one explicitly assumes a node-style callback. That's not going to work on everything.

Something like Promise.wrap(..) (probably Promise.lift(..) or whatever) seems fairly likely to land in the future, because of its obvious utility and its near ubiquity in promise libs already.

If/when that happens, I can almost 100% guarantee they're going to assume "node-style" (aka "error-first style") callbacks, because of how wide-spread that pattern is (if nothing but for the node crowd). I have never found a clean way of making a polymorphic utility which can automatically determine if the expected callback is of that type or another (like split-callbacks, etc).

You'll either have a built-in utility that makes the base-case simplifying assumptions -- most likely, I think -- or they'll conceive of something a fair bit more complex, perhaps like the O.dP property descriptors stuff, or like my ASQ.wrap(..), which lets you configure for things like params-first vs params-last, error-first vs split-callbacks vs simple-callback.

My point is that making such an assumption for my illustrative conceptual Promise.wrap(..) is no more dangerous than any other possible approach I could have chosen here.

The assumptions greatly simplify the illustration, which makes it more teachable. Teachability in a book is generally prioritized over real-world-applicability, at least by me.

getify commented 9 years ago

worth talking about the ES6 ... spread operator vs a spread method,

Not sure how the spread operator would help here specifically? Can you give an example? I think ... would operate in the opposite direction of what I need for the illustration, but maybe I missed something? I think what I really need there is destructuring...

and vs destructuring?

I cover both the "destructuring-assignment" (var [x,y] = ..) and "parameter-destructuring" (function([x,y]){..) forms. Is more needed/relevant? Can you be more specific?

ljharb commented 9 years ago

Gotcha, you're right on the ducktyping test.

Fair point on the confusion around setTimeout clamping.

The error thing was just an offhand reference to your belief that the never-throwing behavior of ES6 Promises is a footgun - which iirc is why you made asynquence throw unhandled errors by default?

If Promise.map is common, then the spec will never be able to name it that. I agree the water is poisoned but making it worse isn't justified. I'd go with promiseMap myself - the language won't be adding new globals that often so that's less of a concern. If you're trying to demonstrate a function, going out of your way to put it on the Promise global, for example, doesn't add anything to the example - but it definitely adds a potential copy-paste hazard to somebody in the future. var promiseMap imo doesn't add that hazard.

Hmm, I'm not sure what I was thinking about the spread operator when I wrote that - disregard that one :-)

getify commented 9 years ago

If you're trying to demonstrate a function, going out of your way to put it on the Promise global, for example, doesn't add anything to the example

It has a symmetry with Promise.all(..) and Promise.race(..), and it's also where lots of other libs are putting those types of helpers/abstractors. That's really the main reason for Promise.map vs promiseMap. I'll contemplate switching back, but I'm still not terribly convinced there's a big delta in hazard vs learnability.

getify commented 9 years ago

@ljharb if you have more comments on the rest of the book (it's all draft complete now), would definitely appreciate them! :)

ljharb commented 9 years ago

Thanks, I'll add here when I do, but don't wait on me :-)

getify commented 9 years ago

last call on editorial comments. :)

ljharb commented 9 years ago

I'm not going to have time before you get this published :-) thanks for listening to the comments on the first 3 chapters!

getify commented 9 years ago

<3 thanks for the help!!! :)