
How JavaScript works: Event loop and the rise of Async programming - kiyanwang
https://blog.sessionstack.com/how-javascript-works-event-loop-and-the-rise-of-async-programming-5-ways-to-better-coding-with-2f077c4438b5
======
galaxyLogic
One thing to note is that async programming like in JavaScript is "threadless"
programming which means all your data-structures are by definition thread-
safe.

It's one less complication in your logic when you don't have to think "what if
some other thread modifies this data while I am doing it?". And you never get
hard-to-detect and hard-to-fix errors caused by that nor do you need to try to
set up locks that might cause performance problems.

So some might say that threads and locks and synchronization primitives are
the solution while others might say they are the problem.

~~~
konschubert
Stupid question. Does this mean that a web server written in node is running
single-threaded? I know that there are callbacks and promises to hand over
control between different execution flows. But doesn't running on a single
thread put an upper limit on the amount of work a server can handle?

And, if the solution is to spin up more servers with access to the same
database, doesn't that mean that we are now having multiple threads accessing
the database concurrently? Much like, say, Python Django?

~~~
nitely
> Does this mean that a web server written in node is running single-threaded?

Yes.

> But doesn't running on a single thread put an upper limit on the amount of
> work a server can handle?

Is not about how much work it can handle, it's about how much it can offload.
Async servers can handle a much greater volume of I/O bounded tasks. So it can
handle more connections. When the task is CPU bounded you can either create a
thread (which does not really scale well) or offload it to some other servers
that can scale horizontally (i.e doing micro services)

> And, if the solution is to spin up more servers with access to the same
> database, doesn't that mean that we are now having multiple threads
> accessing the database concurrently? Much like, say, Python Django?

Yes, to take advantage of all CPU cores you have to create more server
instances. But why would they talk to the same database instance? It could be
a replica or a shard. You can even have a pool of shards connections per
server instance.

~~~
amelius
The problem with offloading to a thread is that you can't structurally share
data with another thread. So any copying of e.g. arguments can be very
expensive, and awkward/inconvenient as well.

~~~
fauigerzigerk
If the other thread/process is running on a different machine (or you want to
keep that option open) that's what you're going to have to do anyway though.

~~~
amelius
Yes, but that's a big "if".

Sometimes you want to use a thread to keep the CPU from blocking (e.g. long
database operations).

Also, sometimes you want to use the CPU to its full capabilities.

~~~
fauigerzigerk
_> Sometimes you want to use a thread to keep the CPU from blocking (e.g. long
database operations)._

On the contrary. That's the prototypical use case for non-blocking event based
IO. No threads needed.

 _> Also, sometimes you want to use the CPU to its full capabilities._

You can use all CPUs by using multiple processes. That's not an issue.

Threads are useful when you want to run multiple algorithms in parallel on the
same bigish in-memory data structure, especially one that has a lot of
pointers.

Something like an in-memory graph database or a desktop application that lets
you work with huge files in-memory, or even complex user interfaces. For
instance, I'm not convinced that the cross process bridging that React Native
has to do is a great idea.

So yes there are use cases where threads are very beneficial. But on the
server side it's essentially the database/analytics systems themselves, not
the code that accesses them.

------
lkrubner
I'm seeing a lot of confusion about the internals of NodeJS, so I hope to
clarify:

NodeJS userland is effectively single-threaded, but NodeJS is not single-
threaded. NodeJS outsources some event loop scheduling to libuv, and libuv is
multi-threaded (by default, 4 threads, but this can be configured. See:

[https://medium.com/the-node-js-collection/what-you-should-
kn...](https://medium.com/the-node-js-collection/what-you-should-know-to-
really-understand-the-node-js-event-loop-and-its-metrics-c4907b19da4c)

An excerpt:

"Libuv by default creates a thread pool with four threads to offload
asynchronous work to. Today’s operating systems already provide asynchronous
interfaces for many I/O tasks (e.g. AIO on Linux). Whenever possible, libuv
will use those asynchronous interfaces, avoiding usage of the thread pool. The
same applies to third party subsystems like databases. Here the authors of the
driver will rather use the asynchronous interface than utilizing a thread
pool. In short: Only if there is no other way, the thread pool will be used
for asynchronous I/O."

It's possible there is some internal enforcement, inside of libuv, of
timeouts, because there are some bizarre problems that come up when NodeJS is
under enough load. See:

"A surprising NodeJS failure mode: deterministic code becomes probabilistic
under load"

[http://www.smashcompany.com/technology/a-surprising-
nodejs-f...](http://www.smashcompany.com/technology/a-surprising-nodejs-
failure-mode-deterministic-code-becomes-probabilistic-under-load)

~~~
iaml
On a similar note, would this kind of offloading be feasible in browsers?

~~~
esprehn
Yes, browsers already work this way today.

For example in Chromium there's threads for GC, JS compilation, IO, image
decoding, audio processing, video decoding, rastering, compositing,
animations, indexeddb, and more. It also uses a thread pool and a task
scheduler that handles prioritization both on the main thread (where the DOM
is) and inside the thread pool. (ex. Handling touches is usually more
important than handling xhr on the main thread).

Other browsers like Servo and Firefox Quantum use the thread pool for handling
layout and style tasks as well.

------
jstimpfle
Some hard earned wisdom: Callbacks (whether asynchronous or not) have the
advantage of being extremely modular. The callee can just do what needs to be
done - since the contract is simply a call to a function with a very simple
signature, this will hardly ever be a (dependency / maintenance) burden.

On the other hand,
[https://twitter.com/sempf/status/917962985582231552](https://twitter.com/sempf/status/917962985582231552)

I suppose experienced developers do as little as possible in callbacks
(basically store the event away and make a note that there was something, at a
central place). Then try to do everything else in easy to understand main
control flow, some time later.

~~~
ambrop7
> I suppose experienced developers do as little as possible in callbacks

(talking about C/C++ mostly)

I have personally come to embrace the opposite approach: let callbacks do
absolutely anything someone could do outside of a callback. This way, code
gets simpler and clearer. For example, if a Socket object contained in
HttpConnection calls back to HttpConnection telling it the connection is dead,
the HttpConnection class should be able to just do something like
"server->connections.remove(this); return;". Yes that remove is HttpConnection
self-destructing; after that statement HttpConnection and the Socket no longer
exist.

Ensuring safety of this is really simple: whenever you call a callback, the
next statement must be "return" and more specifically you must return back to
who called _you_ without touching anything which might be dead now. Or perhaps
more idiomatically you can "return callback();" when both the callback and the
calling function return void.

In the case that you would want to do something after the callback is done,
instead ask the event loop to call you "soon", and make sure your destructor
will cancel that. For example this can be just a timer with zero timeout, or a
special event queue designed for this purpose (in which case LIFO scheduling
will get you natural "recursive" evaluation order).

~~~
passthefist
> Or perhaps more idiomatically you can "return callback();" when both the
> callback and the calling function return void

At least in JS I think that is idiomatic, it's an option for linting:
[https://eslint.org/docs/rules/callback-
return](https://eslint.org/docs/rules/callback-return).

------
tomxor
Async is necessary sometimes... but I'm really hating the trend of designing
APIs that make it unavoidable, with the assumption that it's necessary always.
It just makes code illegible and thus buggy.

~~~
dboreham
Threads were invented decades ago to resolve this problem. Or actors /
goroutines / CSP if you want to call them that..

~~~
xroche
And strangely enough, people still reinvent the wheel, reproducing a scheduler
in userland, which is both insane in term of code complexity and
debuggability, and in term of performances.

~~~
dboreham
Yes, although to be fair they tend to do that because kernel threading never
quite does what it needs to do (e.g. doesn't scale to the # threads
applications need these days).

Your comment is spot on though: I'm only aware of two modern implementations
that really work : Erlang and Go.

~~~
xroche
Threads do scale. On Linux, O(1) scheduler solved this non-issue a long time
ago.

~~~
signa11
> Threads do scale. On Linux, O(1) scheduler solved this non-issue a long time
> ago.

yup they do.

till you start making sure that your code doesn't end up with deadlock, data-
corruption, races, performance issues due to lock-contention etc. etc.
designing efficient locking schemes is notoriously hard alternating between:

\- too coarse grained : resulting in serializing activities which could have
(should have) proceeded in parallel, thereby sacrificing performance and
scalability.

or

\- too fine grained: with space+time for lock operations sapping performance,
error recovery and not to mention understanding etc. etc.

In the former we have the dragons of deadlock and livelock roaming freely, and
in the latter we have race conditions. Somewhere in between is a razor's edge
which is both efficient and correct.

Almost always, things start with ‘one big lock around everything’ and the
vague hope that performance might not be abysmal. When that is dashed, big
lock gets broken up, and the prayer is repeated. Each iteration increasing
complexity and decreasing lock-contention, and hopefully with some luck,
modest performance gain as well.

remember this:

What do we want ?

Now !

When do we want it ?

Fewer race conditions !

have fun :)

~~~
throwme211345
I see your design is lacking and your fud quotient is high. Good on you!

~~~
cheez
Yep. This was my reaction as well. Threads are fine.

------
Leka74
Lua coroutines are a much better & simpler solution to handling async
callbacks (and callback hell) than what JS offers. Here's a simple wrapper I
made for it:
[https://gitlab.com/snippets/1678104](https://gitlab.com/snippets/1678104)

~~~
pharke
Not really seeing the advantage over modern JS async, perhaps you can explain
your assertion?

~~~
wruza
Js:

    
    
      async function bar(){...}
      function baz() {...}
    
      async function foo() {
        var x = await bar();
        var y = baz();
        return x, y;
      }
    

Lua:

    
    
      function bar() return something_that_may_yield_deep_inside(...) end
      function baz() ... end
    
      function foo()
        local x = bar()
        local y = baz()
        return x, y
      end
    

Iow, in Lua it is irrelevant whether something yields or not, so you don’t
care if it is async.

~~~
parenthephobia
But I think I do care.

When I see

    
    
        var x = await bar();
    

I know that other code, outside bar(), may have run during the execution of
that statement.

Also, the JS approach makes composing asynchronous operations simple:

    
    
       var x = bar();
       var y = bar();
       return await x, await y;
    

Both bar invocations can run in parallel. If, say, each invocation of bar
fires off an Ajax request that takes a few seconds to come back, that can be a
significant saving.

It's unclear (to me) how that would be done in Lua without complicating the
API.

~~~
gpderetta
It is a false security. If you call a function either you know what it does
(so in lua you would know whether it call yield) or you don't and it could
still be calling arbitrary code, so you have to code defensively and think
about reentrancy. The only reason for await is that it normally needs to save
less state (a single frame) than a full coroutine yield (a whole stack).

Also lua-like stackfull coroutines don't prevent firing multiple asynchronous
operations at the same time (like in your example), they only make the waiting
much more peasant.

------
jorgeleo
“Event loop” pfff. windows 3.1 was doing event loops before it was cool

~~~
srean
I think the prize for doing event loops without a big fuss and before it was
cool probably should go to tcl. They also did multithreading right, whereas
Python botched that up. Tcl model: if you need to, run interpreters in
separate threads in the same address space. No need for GIL, no need to pickle
uncle pickle every message.

~~~
glhaynes
_> pickle uncle pickle_

Is this … a Rick and Morty thing?

~~~
srean
<sheepish grin> Autocorrect kicked and I did not notice. Let me leave it as it
is for the comedic effect.

------
edem
When will JS programmers pull their head out of the sand and realize that this
is nothing new? All this stuff was present in Windows 1.0 and they moved on
since. Now we have multi-reactors like vert.x and concurrency models based on
process philosophy (Clojure). Please wake up.

------
he0001
The first example in this article is misleading, imo, since the actual
execution is synchronous, not asynchronous. The "programming style" is
asynchronous, yes, but the execution will ALWAYS be the way its described
since there are no other way it could be executed and therefore synchronous.
Unless there are any way of things to happen out of order in relation to the
execution flow, its effectively synchronous. If there is an possibility
something can be executed out of order, its asynchronous. JS doesn't provide
that because the event loop. It will always be executed in the order of the
loop and that will be synchronous (even if and event re-queued because it's
not done yet).

~~~
emmab
Some databases are able to provide serializability while not grabbing a global
synchronous lock for all transactions.

Even if the execution must appear synchronous to an observer, it's sometimes
possible to do some async work.

That could be because it's performing an operation which is atomic to the JS
application (e.g. a sort operation can be implemented with parallelism because
the JS app is paused the whole time it's running).

Or, the JS interpreter could perform static analysis on control flow and
determine that two or more computations do not depend on eachother for a
period of execution.

~~~
he0001
Of course the they can, but that's not the thing here and it's about JS. The
"lock" here is the event loop, and that's the issue.

I don't understand the part where the database is doing work is in any way
related to what the JS app is doing. Even if you are running on the same
machine, they are bound to have different runtime scopes and not related on
how they execute.

~~~
emmab
The event loop doesn't strictly have to act as a global lock. It just has to
appear to act as a global lock to any observer.

~~~
he0001
Can you describe where this would be the case?

~~~
emmab
That could be because it's performing an operation which is atomic to the JS
application (e.g. a sort operation can be implemented with parallelism because
the JS app is paused the whole time it's running).

Or, the JS interpreter could perform static analysis on control flow and
determine that two or more computations do not depend on eachother for a
period of execution, and can thus be performed in parallel.

Or, as long as the JS isn't performing IO, the execution environment can use
optimistic concurrency[1] and back out the changes if the codepaths did have
interdependency or tried to perform IO.

[1]
[https://en.wikipedia.org/wiki/Optimistic_concurrency_control](https://en.wikipedia.org/wiki/Optimistic_concurrency_control)

~~~
he0001
But that is still not asynchronous in relation to the program flow. Running
other unrelated tasks is not asynchronous like that, it's concurrent
execution. Asynchronous would mean that something that share context, time or
execution, is run out of order in relation to each other. And even if it's
using optimistic concurrent execution, in relation to program flow it's still
synchronous. The event loop is still synchronizing program flow, even if it
does optimistic concurrency control. Concurrent doesn't mean "out of order",
it means "at the same time".

------
hyperion2010
Chiming in a bit late here, but for years I have said that there are really
only two kinds of UI that I want: a cli REPL (maybe with something like
ncurses), and a full powered 3d game engine. Everything else in between is
going to be a poorly implemented version of one or the other of those two, so
just pick which end of the spectrum you want to be on for the purposes of
rendering and user interaction, and build your browser apps like cli/ncurses,
or implement them inside of quake3 and force your users to login by learning
how to plasma surf.

~~~
throwaway613834
How in the world would a UI like that of (say) Microsoft Word be a "poorly
implemented version of a CLI REPL or a 3D game engine"?

~~~
kuschku
MS Word has a full 3D engine for text effects.

~~~
throwaway613834
Sure, and you can type text with it just like in a CLI. But how does that
support the claim, is the question.

~~~
kuschku
I’m not sure, I just hoped maybe providing some context may shed some light on
it.

------
flavio81
The article is a well written, well illustrated guide to programming under
Javascript using an event loop, callbacks, etc.

Which I personally find awkward to do in 2017 where the OS or the language
runtime should give me better concurrency facilities.

~~~
erokar
And there are runtimes that do that, like Erlang's BEAM. At least Node
concurrency is a step in the right direction, as it's much easier to reason
about than threads on the JVM.

~~~
kuschku
JS is just introducing threads with shared state right now. Node concurrency
is now as easy to reason about as JVM concurrency.

Except, on the JVM, all these issues were solved years ago, and we nowadays
have amazing libraries to deal with all the issues for us.

~~~
pitaj
There is a proposal for shared memory and atomics, which are absolutely opt-in
at the moment. You can easily use the event loop if you don't need parallel
processing.

~~~
kuschku
And you can use an event loop in Java today if you want, the question is more
about library support.

And that is certainly changing in both alnguages now, in Java towards
promises, in JS towards threads.

------
speedplane
The hardest bugs to track down and fix are often race conditions... async has
fewer than pure parallel but it doesn't eliminate them.

------
kaycebasques
Can someone provide me code examples of the “job queue”?

