Live data from Hacker News

Promises are not neutral enough

staltz.com

1–10 of 133 posts

Re: Promises are not neutral enough

#3
Agreed 100%. I did a bunch of js work about 3 years ago, used tons of promises. Then started a new job using Scala. The futures api in Scala is exactly what the author advocates, and it is definitely better for the reasons he gives.

Re: Promises are not neutral enough

#4
This was a weird blog post to read. I think I agree on all of your points (Promises should be lazy[-ish], cancellable and optionally synchronous) but disagree on all of your proposed solutions.

I do think `p = new Promise(fn);` shouldn't kick off the `fn` immediately. But that it should start right away in the next event loop. I haven't had issues with creating promise getters for repeatable calls. And think it organizes the business code away from the low level code.

I don't see a problem with the original Promise.cancel() you proposed or how your lazy promises makes canceling them any easier.

And don't we have `await` for the synchronous problem?

  console.log(await Promise.resolve('hello')); 
  console.log('world')
  // outputs "hello" "world"

Re: Promises are not neutral enough

#5
This may get me in hot water here but...

I started working with JS promises specifically when they were barely available in a beta runtime. It took me over a year of working with them to really get a feel for them, now it's been far longer. That's because while you can "understand" the description and use it just fine, but a deeper comprehension and intuition takes much more time. I experimented a lot and insisted on writing my own helpers from scratch, without looking up other people's code, because I wanted to get a feeling for the details.

This article seems quite artificial to me, the problems mostly made-up.

I don't see the point of the first complaint. If you don't want to start right away chain it to something that it should wait for. If it should not wait, then it can start right away. Her writes "Functions rescue us in this case because functions are lazy." which I don't quite understand: what is he running through promises if not functions? Hi "betterFetch" example mixes synchronous and promise syntax - how about using async/await if you prefer the former? I admit though I don't quite get the point of that example.

I don't understand the whole "run a promise" idea either - because you don't "run a promise", that whole notion has nothing to do with what "promise" means. Just look at the word! It represents a (wrapped) future value. Where does the idea of "running it" come from? How do you "run" a (future) value?

You have a function and it is quite easy IMO: Using a promise you chain it to whatever you want to wait for. These days you can even use semi-synchronous syntax (async/await). "Running a promise" makes no sense to me, you run functions, and I don't see where the difficulty lies here?

The second point, cancellation, has been discussed very, very thoroughly - after all, this was on the table to be standardized. One of the issues he raises is the same as point one - if you have a chain it's automatic. The main issue of cancellation is that you have zero control over the actual asynchronous operation that the promise actually stands for - because this is controlled by the OS alone! If you started I/O, what does "cancelling the promise" mean?

1. If it is still waiting: If you don't want to run something make sure the previous step returns a rejected promise. You can easily "cancel the promise". Just let your promise function check something in the parent scope (via callback or it is in its lexical scope) when its chained function starts, and if that says "you are canceled" then don't do it. You can put such a check as a standalone function anywhere in the promise chain you created, just let that "amIcancelled()" function throw or return a rejected promise. The whole chain aspect is something that the article is missing.

2. If the code is already running: you cannot cancel the actual (OS controlled) asynchronous operation, nor can you cancel a running JS function (unless you use async/await see bottom paragraph).

I agree that promises are not perfect, but async/await - not mentioned at all! - makes it a bit easier for many people - as long as they don't forget one thing: Even if your functions now look like synchronous ones there is a fundamental difference: A synchronous JS function is never interrupted by any other code. An async function is suspended and other JS code gets to run in the middle of it when it encounters an "await". This is something new first introduced by generators, before that JS functions were atomic (now some are not).

Re: Promises are not neutral enough

#6
post #2

The alternatives look nice. There's just one requirement missing: streaming progress information to the listeners.

I think this completely changes what you have though, in a way that's no longer a primitive?

You then have something like an async generator, which is like a fusion of asynchrony and sequence?

Except generators are pull not push, so instead you have a promise that accepts a function that operates on a sequence?

I don't know if this pattern is common somewhere, someone who does please explain!

Re: Promises are not neutral enough

#7
post #4

This was a weird blog post to read. I think I agree on all of your points (Promises should be lazy[-ish], cancellable and optionally synchronous) but disagree on all of your proposed solutions. I do think `p = new Promise(fn);` shouldn't kick off the `fn` immediately. But that it should start right away in the next event loop. I haven't had issues with creating promise getters for repeatable calls. And think it organ…

One nice thing about "new Promise()" calling the function immediately is that if you are prepared to provide the value immediately then you don't have to return into the runloop, but probably the reason I'd give for why delaying the call would be a horrible idea is that the vast majority of the time the promise is going to do some minimal amount of setup work and then... return to the runloop (and if it isn't, I am going to ask why you are using a promise). That means that the behavior they currently have of calling the function immediately minimizes returns to the runloop and provides performance as close as possible to what you would get if you hand-coded it using callbacks (the only overhead being the unlikely-to-be-optimized-away-fully-by-the-VM object allocations and indirect function calls; but like: this paradigm in a language with zero-cost abstractions would be perfect).

Re: Promises are not neutral enough

#8
If promises were lazy (basically just function composition), it seems like we'd have similar problems to Haskell where it's difficult to understand performance. You wouldn't know when an I/O operation starts or whether it will get executed again. Maybe that's okay for a high-level API, but low-level I/O operations are not idempotent, so this seems risky?

So this looks like a trade-off: you could make function composition easier only by making Promises less suitable for their original purpose. By going generic, you lose an important guarantee that a Promise is just a value.

Re: Promises are not neutral enough

#10
I think the author wants promises to represent computation, whereas they represent predetermined (i.e. single-shot) events. He mentioned C# Tasks, which do mainly represent computation, but in some cases Tasks are also used as events and this gets confusing as hell. I've worked with C# Tasks and hope that MS once cleans this up and builds the stuff on promises instead. Note that the C# language construct uses the awaitable pattern (GetAwaiter method) instead of tasks - awaitables are actually pretty similar to promises.

1. Eager, not lazy - I think it was a mistake for the promise constructor to take a function, and in that way lead the users to believe the promise represents a computation. Creating a pair of promise and future (the latter as the producer side, like in C++) would be much cleaner. I disagree that lazy would be more general, you can simulate lazyness with functions, but you couldn't eliminate the performance cost of creating the unnecessary closure with a lazy solution. Regarding getUserAge - the common case for that function would be to take the user ID as the parameter (and hence would be lazy by construction), the parameterless version is a special case.

2. No cancellation - cancellation is much better represented with cancellation tokens (even C# Tasks cancel with cancellation tokens, so does fun-task mentioned at the end, though in non-composable way) - you cannot build a generic solution that can cancel the right computations. With cancellation tokens it's clear what cancels what.

3. and 4. (as well as being allowed pass non-promises to places where only promises make sense, like Promise.all and await) are unfortunate accidents that make typed environments (e.g. TypeScript) harder to work with but are not that important as 1 and 2.

Post reply on HN