Hacker News new | ask | show | jobs
by TheZenPsycho 4827 days ago
The point is promises free you from wanting or needing to know about the order that things happen in. I hear you saying you fear promises, because it means it would get in the way of your ability to know that. But the truth is once you embrace them, that need becomes unimportant.

The idea that webservers are "all about side effects" gives me a chill. The whole architecture concept of HTTP is no side effects, so to claim that it's all about side effects seems odd. It should only be the case for POST PUT or DELETE methods, and only in very specific ways.

4 comments

As someone who's started moving from the imperative world, albeit in Java, C# and Ruby based work rather than JavaScript, I can say that letting go is the hardest part.

However, when you learn to stop worrying and let the runtime decide it's so much nicer. It turns out that people have already optimised the framework, so at worst it's just as fast as the code I wrote. At best it's faster because the framework knows more about what it's capable of.

The biggest thing to realise is that while you can easily make things perform well in isolation the runtime can look at the bigger picture. There's no point making an operation run in 300ms if it blocks all other tasks on the server, when it could run in 600ms and allow everything else to keep going.

Just think of it as forking and joining threads...
> The point is promises free you from wanting or > needing to know about the order that things happen in > ... But the truth is once you embrace them, > that need becomes unimportant

This smells like the classic leaky abstraction though. Like when people tried to paper over the difference between remote calls and local calls with abstract interfaces (CORBA, RMI, etc.). Everyone would say it was so awesome, remote calls look the same as local calls! But it wasn't awesome, it was horrible, because the details of the abstraction 'leaked' through and you got all kinds of problems from having delegated away what was actually one of the most critical, sensitive parts of your code to a layer you had no control over. 15 years later we're back to nearly everybody using REST because it turns out to be way better not to shove those abstractions on top of your most important code.

Now, I'm not saying that analogy is perfect here ... but it does remind me of it. Why should you care about the order of things? Just to suggest something, sometimes it's just useful to be able to reason about it. "We know the first operation definitely happened before the others, so an earlier one failing can't be a side effect of something a later one did ... oops, we don't know that any more. We actually have no idea what order they happened in."

The need for that form of reasoning seems to me to be a symptom of the way that we go about writing code. We don't need to worry about if line 10 executes before line 11 within a given scope -- we just know this. If we come up with a similarly simple way of writing asynchronous operations that have dependencies then we can read it just as fluently.

In my experience, promises do fill this role if they're used in a suitable scenario.

> The idea that webservers are "all about side effects" gives me a chill. The whole architecture concept of HTTP is no side effects, so to claim that it's all about side effects seems odd. It should only be the case for POST PUT or DELETE methods, and only in very specific ways.

There's nothing incongruous about that. It is the case that side effects should only happen on POST, PUT, and DELETE methods (and the like), but almost all webservers are written because of a need to use these.

If your webserver is all GETs and HEADs, then it is either trivial and you would have used someone else's instead of writing your own, or its sole purpose is to repackage and serve existing data from other sources - a rare use case among all webservers.

If you were to take an inventory of all the webservers out there, you would doubtless find that almost all of them exist in large part in order to create side effects.

And I'm thinking about complex sites, not simple serve-up-a-page-and-that's-it.

A cache gets refreshed or added to. A user's viewcount is incremented. A new statistic is calculated and then stored. An item is marked as viewed. And these are all just on a GET.

On complex sites with a logged-in user, side effects are pretty much the norm.

I'm not arguing about that, I am arguing about "all"
I think his point is that there's usually a very strict ordering to the events on an HTTP server - you parse and sanitize your input, make some database calls, and generate a response - at best, letting something else do the sequencing and composition for you doesn't gain you much, as it might in a reactive GUI. At worst it leaves room for subtle bugs or code that's less clear (arising from the statefulness of the Promse object itself).

Using a Promises, as opposed to reducing a list of computations async-style, also limits you to the Promise object's interface, so you lose (or at least add cruft to) the flexibility and composability of using native lists. By sequencing computations with lists, if I want some insight into what's happening, I just List.map(apply compose, logFunc). With promises, I have some work to do.

Promises have their uses, but it's definitely a tradeoff, and for most HTTP servers, I'd argue that their utility does seem a bit limited. I'd similarly say that making a point of using FRP to build a server would probably be a bit overkill for the task.

What kind of object is in your list of async operations? promises. (though probably your own ad hoc, hand rolled and poorly specified version of them)
Just plain-old native functions - that's the whole point.
when you put "plain old native functions" in an array, with the intent of executing them in sequence, with the output of i being fed into the input of i+1, congratulations, the functions are now implicitly promises.

Because, in the end, what, semantically, is the difference between:

runqueue([func1,func2,func3,func4]); and func1().then(func2).then(func3).then(func4);

No significant difference at all, really. except the promises permit you much more flexibility and options.

The difference is that the first works with all of the native list functions, as well as all of those in e.g., underscore, without any extra work. The latter doesn't. Now, the latter certainly offers some other features, but my point was that, in specifically building an HTTP server, it's been my experience that those features aren't of as much use as being able to use the native list functions to, say, map a log function onto the list of functions, or reduce while halting execution under particular conditions.