Closed ljharb closed 9 years ago
+1
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:
- 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:
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:
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.
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.
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?
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(..)
.
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.
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?
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 :-)
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.
@ljharb if you have more comments on the rest of the book (it's all draft complete now), would definitely appreciate them! :)
Thanks, I'll add here when I do, but don't wait on me :-)
last call on editorial comments. :)
I'm not going to have time before you get this published :-) thanks for listening to the comments on the first 3 chapters!
<3 thanks for the help!!! :)
Preface:
Chapter 1:
data.map( function(val){
instead of the more commondata.map(function (val) {
, and the spaces inside the parens for theajax
calls. Is that intentional? Even if it's your preferred style, and if it's consistent, you still may want to consider using more common whitespacing forms to avoid reader confusion.splice
, but that's one of the most confusing Array methods, and mutates the array. Is there another way of writing that code that doesn't use splice, or mutation? I think it would read much more clearly.setTimeout
can't possibly schedule any sooner than 4ms, and prior to HTML5 it was like 20ms?Chapter 2:
Chapter 3:
.catch
in ES6?if (p && typeof p.then === 'function')
- which also would cover a primitive if i'd set upNumber.prototype.then
, for example (not that that would be a good idea).on
notation is jquery/backbone/etc standard, but isn't part of the language or the DOM, so it might not be helpful.Promise.race
(like you did withPromise.all
) would be helpful herePromise#catch
is so useful - it would be great for it to have been introduced above."The point of promises is to give us back functional composition and error bubbling in the async world." https://gist.github.com/3889970
somewhere, it's awesomePromise.race()
rejects butPromise.race([])
stays pending forever. i'm not on board with the error throwing stuff you believe, but this one i agree is utterly insane.Promise.map
, for example). That stuff is widely considered a horrible practice, and with no immediate notes every time you do it advising that it may be a bad idea, people will copy it, to their detriment.es6-shim
? :-)foo
doesn't return two values, it returns one, an array. that there's two things inside it is irrelevant. i think you're blurring the line here a bit too much....
spread operator vs aspread
method, and vs destructuring?Observables
are proposed for ES7, might be worth mentioning https://github.com/jhusain/asyncgeneratorChapter 4: