18 ms·
Promises have always been extremely contentious in JS for a couple reasons and Domenic has had the patience of a saint getting the original proposal through so
by cwmma 10y ago
Promises have always been extremely contentious in JS for a couple reasons and Domenic has had the patience of a saint getting the original proposal through so it's totally understandable that he might not want to deal with this anymore especially if people in his org are against it.
There were a couple reasons the original promises were so contentious to standardize.
- Monads: there was a very vocal faction on TC-39 that really wanted a Promises to be monadic and very much emphasized mathematical purity over usefulness and any attempt to discuss things tended to get side tracked into a discussion about monads, see brouhaha over Promise.cast [1].
- Error handling: Promises have a bit of a footgun where by default they can swallow errors unless you explicitly add a listener for them. As you can theoretically add an error listener latter on all other options had downsides (not always obvious to advocates) leading to a lot of arguing, must of it after things had shipped and realistically couldn't be changed. Fixed by platforms and libraries agreeing on a standardization of uncaught promise events [2].
- Just an absurd amount of bike shedding probably the 2nd most bike shedded feature of the spec (behind modules).
So cancelable promises reignite all the old debates plus there's no obvious right way to do it, like should it be a 3rd state which could cause compatibility issues and would add complexity, or should cancellations just be a sort of forced rejection, which would be a lot more backwards compatibility but with less features.
Additionally there is a bizarre meme that somehow observables (think event emitters restricted to a single event or synchronous streams that don't handle back pressure) are a replacement for promises or are a better fit for async tasks they should be used instead.
edit: patients => patience
1. https://esdiscuss.org/topic/promise-cast-and-promise-resolve https://esdiscuss.org/topic/promise-cast-and-promise-resolve
2. https://gist.github.com/benjamingr/0237932cee84712951a2 https://gist.github.com/benjamingr/0237932cee84712951a2
- TheAceOfHearts 10y agoPromises work fine in some cases, but absolutely I hate how exceptions and errors are grouped into the same bucket. Oh, you're handling 4XX responses as errors? Well, now you get exceptions there as well, good luck! e.g. If I'm sending some data over the network, I want runtime errors to cause activity to be aborted and logged. But if I get a 429, I'll want to wait and retry.
- spion 10y agoWhich is why you write a catch combinator to filter out only the errors you want function ifErr(predicate, handler) { return e => { if (predicate(e)) return handler(e); throw e; } } function codeIs(n) { return e => e.code == n } promise.catch(ifErr(codeIs(429), e => handle error here))
- paulddraper 10y agoWhat treats a 429 response as an error? XMLHttpRequest doesn't use promises, and the Fetch API treats all HTTP responses -- including 429 -- as success. I think you have a problem with a library.
- y4mi 10y agono, libraries should not throw exceptions if they get a http response. that is a perfect example where the programmer has to roll his own, because this is entirely situational. the pseudo-code example of the grandparent seems to me to implement a) detecting the http code, b) throwing an exception and finally c) handling this except.
- spion 10y agoWhich pseudo code are you referring to? Exceptions being for "exceptional" circumstances is a popular misconception. Exceptions are for "an upper layer to handle". You can use them for any alternative values you wish to propagate to the upper layer.
- paulddraper 10y ago> Exceptions are for "an upper layer to handle". So are return values :) The difference is that return value is for the next immediate layer; an exception is for some layer, but I don't know what. IMO your HTTP layer should not be throwing exceptions based on status code. The layer above that should.
- koolba 10y ago> Promises have always been extremely contentious in JS for a couple reasons and Domenic has had the patients of a saint getting the original proposal through so it's totally understandable that he might not want to deal with this anymore especially if people in his org are against it. I know this is probably a typo (patients => patience) but that spelling seems to work as well. > So cancelable promises reignite all the old debates plus there's no obvious right way to do it, like should it be a 3rd state which could cause compatibility issues and would add complexity, or should cancellations just be a sort of forced rejection, which would be a lot more backwards compatibility but with less features. IMHO neither as cancelability isn't a common requirement. People that need it tend to have more complex requirements and end up tying the cancellation to other chains of logic anyway. Also, unlike say catching an error, you generally want all cancellation handlers to fire rather than just the first one (like with errors).
- cwmma 10y agofixed the spelling and you're right it's not a super common requirement, but it comes up in context of designing APIs to use promises, and if you are trying to add promises to an API that is cancelable, you'd want some way to express that.
- Rapzid 10y agoDo you have any idea what the current direction is(if any?) that they are moving in? I'm mostly familiar with the use of tokens; Golang using Context and C# using CancelationToken. I currently utilize cancellation tokens myself in nodejs(TypeScript) when creating libraries or wrapping the stdlib event/stream based stuff.
- cwmma 10y agoWith this canceled there is no official direction, and any further movement will likely be based on what is decided in the community.
- zbjornson 10y agoIt's fairly common to cancel an HTTP request. The new fetch API doesn't let you cancel a request as a consequence of this shortcoming of promises, while XMLHttpRequest does.
- RodericDay 10y ago> Promises have a bit of a footgun where by default they can swallow errors unless you explicitly add a listener for them. "a bit of a footgun" is such a huge understatement. Now that I know about this, I'm skeptical too.
- cwmma 10y agoand you've missed the last part of that paragraph where I mention unhandledRejection events becoming a defacto standard which straddles a nice line between avoiding false positives (error listener attached latter in the program) and errors going into a black hole
- Rapzid 10y agoNot sure if that's gone in yet so I've continued using Bluebird for my stuff. You can(and should!) create a global handler for unhandled rejections. It's almost always a bad sign if you get one.
- RodericDay 10y agoThen don't call them "a bit of a footgun". It's a huge issue, in the process of being maybe fixed, and underplaying it to argue your side does not help matters.
- spion 10y agoIts already fixed. Thats why it was just "a bit" of a footgun i.e. not unfixable.
- Nekorosu 10y ago> Monads: there was a very vocal faction on TC-39 that really wanted a Promises to be monadic and very much emphasized mathematical purity over usefulness I don't think this was the case. The main argument of FP side was having more composability built into the core of the language.
- staltz 10y agoWhy do you say "bizarre meme"? Observables are not like event emitters. RxJS Subjects would fit that description. See http://stackoverflow.com/questions/25338930/reactive-programming-rxjs-vs-eventemitter-in-node-js#35239241 http://stackoverflow.com/questions/25338930/reactive-program... and https://www.youtube.com/watch?v=uQ1zhJHclvs https://www.youtube.com/watch?v=uQ1zhJHclvs Observables are just an API for any push system based on callbacks, to make it composable and lazy. Array.forEach uses callbacks, Promise.then uses callbacks, Node.js streams are consumed using callbacks. And observables can model those cases without losing their properties (e.g. Array.forEach is called synchronously).
- cwmma 10y ago> Why do you say "bizarre meme"? the bizarre meme is that they are 'replacements' for promises, they are really intended for (as you rightly put it) push streams which is a different, though occasionally overlapping, use case then promises.
- staltz 10y agoI said push system, not push stream. Promises are a push system.
- spion 10y agoIf there was a specialized Observable that is guaranteed to only generate one value per subscription, and the API indicated this clearly by naming such an observable "Task" or "Future", I'd be all for it. Otherwise, its like using Arrays or Lists instead of Maybe. Hope that clears up why its "bizarre", but if not, its because the contract is not restrictive enough. Example: `let x:Observable<Row[]>`. Does this generate a single event with all the rows? Or does it do row batching? No idea. Can I consume both the same? Not really, depends on the use. e.g. if I want to sort the items, I'd have to implement merge sort for the second case.
- int_19h 10y agoI think the argument here was indeed to treat promises as a subset of observables, not replace them with observables. Just as option type is a subset of collections that can have at most 1 element (but all operations that apply to any other collection, like say "map", still make sense for it).
- bad_user 10y ago> Monads: there was a very vocal faction on TC-39 that really wanted a Promises to be monadic and very much emphasized mathematical purity over usefulness and any attempt to discuss things tended to get side tracked into a discussion about monads, see brouhaha over Promise.cast Monads are about usefulness, because a type being monadic means there is a bind/flatMap operation you can rely on to have a set of laws. So a Promise being Monadic means that it is lawful and more composable. And it is especially relevant in the context of a Future/Promise. Aren't you tired of Javascript Future/Promise implementations that are broken? Like, please learn about the concept.
- jackmott 10y agoannnd there we go
- kaishiro 10y agoThis is one of the most ironic comments I've ever read on HN, right down to your final knife twist.
- throwanem 10y agoLike, yeah! It's just a monoid in the category of endofunctors. What's the problem?
- depr 10y agoI don't get the value of composability and refuse to learn, and "monad" and "laws" sound like math which I don't want to understand. So instead of attempting to comprehend what this is about I'm just going to make fun of you in a comment.
- throwanem 10y agoI don't see a lot of point in making better programming concepts seem accessible, and "try to meet people where they are" sounds like populism which I don't want to understand. So instead of attempting to argue that there's value in the idea in a way that doesn't put people off with an implicit accusation of lazy ignorance, I'm just going to go with the implicit accusation of lazy ignorance.
- spion 10y agoThe problem wasn't usefulness or purity, it was that the community already standardised on a certain behaviour of promises that was not monadic. The only way to make promises monadic was to therefore not standardise "promises" at all. At that time, monadic tasks were basically unheard of in the community. By the very nature of the monad laws, they cannot have side-effects, so they cannot be eager. This means that a user "running" an operation would observe nothing until they subscribe to it. In an imperative language like JS, this is confusing for users, and arguably not worth the breakage of the existing promise ecosystem. Another nice property of Promises is that they can be implemented in a way that doesn't cause stack overflow for endeless recursion (and additionally, in some cases we can get "tail recursion"). This might be doable with lazy tasks too - not entirely sure. See https://github.com/paf31/purescript-safely https://github.com/paf31/purescript-safely On the other hand, monadic tasks make many other things easier, like * cancellation (just unsubscribe), * synchronous error propagation, so no issues with error swallowing and perhaps no post-mortem issues in node. Nothing runs before the subscription, so at the point the side effects are running, we already know whether there is an error handler. Effectively the `done` method would've been the eqiuivalent of `subscribe`, and mandatory * defining parallel, sequential or limited-concurrency execution order after the tasks have been created
- zeahfj 10y ago> By the very nature of the monad laws, they cannot have side-effects, so they cannot be eager. I'm pretty sure monads can have side effects and be eager. It's just that not everything will be captured by the type signature. AFAIK this does not prevent it from being a monad. You can have monads without any type signature. Also, AFAIK the eagerness is a requirement due to the potential for errors changing program flow that is not captured by the type signature. But AFAIK this does not prevent it from being a monad. AFAIK monads only have to conform to the monad laws, bind and return. I'd like to know if I'm wrong because I'm concerned with the conflation of monads and language features of haskell. Specifically we can benefit from monadic composition without making javascript into haskell. I think Erik Meijer has been rather damaging in this regard. I know I'm in the minority so I'd like to be proven wrong so I can come join the party :)
- jrochkind1 10y ago> So cancelable promises reignite all the old debates plus there's no obvious right way to do it, like should it be a 3rd state which could cause compatibility issues… It seems obvious to me that the 'right' way to do it is a cancelled promise is in the error state, with the error object just being a new type of error CancelledPromise or something. But I guess once you start getting into it, this becomes less obvious?
- cwmma 10y agoBingo, it turns the same thing wasn't wasn't obvious to everyone
- jrochkind1 10y agoweird. it would be interesting to read more of the debate/discussion, is it actually avail anywhere?
- ralfn 10y agoNo, the right way is to fix the fetch() api to not return a promise and reflect on why the system was so broken that fetch() api was released with such an obvious flaw. Because Promises don't need to be cancellable. This has nothing to do with promises. Fetch requests need to be cancellable. But they, in all their wisdom, decided that the fetch API returns a promise. So now promises need to become whatever the hell the fetch API actually needs to return. You would almost think design by committee is fundamentally flawed, and design by community even more flawed. Because the edge cases are never obvious. For every new feature the question shouldn't be "do i make 99 programmers 1% happier?", but do i make 1 programmer cry upto the point where they would stab my eyes out if they could? We've all been that 1 programmer, and yes, i would if i could. It's a clusterfuck of bike shedding. Most new ES2016 have details that haven't been though through and will end up being hated on the same level as '==' and 'NaN'. The dev-leads of the different javascript engines should not just be able to veto. They should be able to veto people out of the room, out of the debate. And we should celebrate it every time they take advantage of that power. Or just give up on Javscript all together and just let the rot fester.
- indolering 10y agoWhile it's great that they were so nice, they are unable to effectively moderate GitHub and ESDiscuss. One ticket for the private member proposal had 600+ comments, some of them crazy stream-of-consciousness posts that should have been shut down. Dealing with this volume of input requires aggressive moderation, community participation guidelines, and canonical explanations of the decision making process.