JavaScript, In My Opinion

I played Diablo 2 religiously in my formative years. Early in the plot you’ll meet Deckard Cain, an old man who pesters you to listen to what he has to say. The irony is not lost on me that in writing this, I’ll become JavaScript’s pestering old man. In the words of my spiritual predecessor: stay awhile and listen.

Despite being the most widely used programming language in the world, most people using it don’t have a clear understanding of how it works. Even worse, those who get it have a hard time teaching their secrets to in a way most people can quickly understand.

I don’t blame either group.

Gave up trying to understand monads for the same reason that nobody has been able to explain it to me in a way I can understand. On the other hand, I’ve never been able to explain to successfully explain to anyone how JavaScript works.

I’d like to share a few simple ideas that will change how you see JavaScript.

The ideas will explain clearly what makes JavaScript slow. With them, you will learn to recognize if the JavaScript you’re reading is slow and learn how to rewrite it to be fast. These ideas are applicable in JavaScript engines as old as the one Internet Explorer 6 uses and are valid today.

If you work with JavaScript, learn these ideas so you can finally enjoy working with this language.

The list is at the bottom of the post, if you don’t want to stay awhile and listen. *sigh*

Yield

This is a trickiest idea to explain. Unfortunately we must start here, because if you don’t have a clear understanding of yielding, you’re truly limited in what you can accomplish.

Q: What is yield?
A: Yield is the process of making your code run asychronously (async).

Q: Running code asynchronously is better?
A: In JavaScript, yes.

Q: Why not write JavaScript asynchronously?
A: Humans think synchronously so we write code that way. Computers appear to read code synchronously, so the code we written to be read understood synchronously.

Q: If humans and computers appear to read code synchronously, why does JavaScript run asynchronously?

We’re about to go down a rabbit hole. Warning you because the beginning is the weirdest part. Everything else is pretty straightforward afterwards. Just bear with me through my probably-wrong understanding of this.

A: Everything is asynchronous but humans can’t think asynchronously. We think of time as a constant, moving in a uniform direction at a uniform pace. It appears this is not the case. Clocks, calendars, computers, even our eyes and brain lie to us about this.

Thankfully, you don’t have to understand time.

You just remember keep this map of a JavaScript program nearby and remember some simple points.

In JavaScript …
1. Asynchronous (async) code performs better than synchronous (sync) code.

JavaScript
1. Your code
2. Runtime
3. Engine

Please keep in mind my usage of these terms and the view of them may not be technically correct, but they should be helpful in understanding yield, and thus JavaScript.

Remember when I mentioned humans think and write synchronously? The code is meant to be executed in the order it is written, and is.

console.log( 'hello' );
console.log( 'world' );

// output
// > hello
// > world

JavaScript
1. Your code [sync]
2. Runtime
3. Engine

The engine runs your code, which it executed synchronously. This means our interactions with the engine are synchronous.

The engine does way more than just run your code. I don’t know anything about the runtime, but I suspect it also gives the engine code to run. Whether it does or not, the engine is very busy ™ translating your code into lower-level code, maintaining the environment your code is running in … it’s insane.

The engine can do multiple things at once, mostly because of the magic of modern computing. I don’t want to get into this just yet, so its easier to say engine is async.

JavaScript
1. Your code [sync]
2. Runtime
3. Engine [async]

Q: What about the runtime?
A: I don’t know, but I’ll assume sync. Thankfully, it doesn’t matter if we don’t know the correct answer.

JavaScript
1. Your code [sync]
2. Runtime [???]
3. Engine [async]

JavaScript the engine is async, but your code can only interact with it in a synchronous way. This means according to the JavaScript spec, you have zero ways of interacting with the engine in a synchronous manner.

Q: Since the spec only allows me to interact with the engine directly in a synchronous way, does this means technically JavaScript is sync?
A: Not really.

Remember we started down this path trying to understand why yield, the ability to run sync code asynchronously, is important? This is it.

According to the JavaScript specs, we can only run code synchronously.

Runtimes, the thing I don’t know or care out, provide us with the functions we need to yield. With yield we can turn run our  code (sync) in the engine asynchronously (performs better).

A long time ago, some runtime gave us setTimeout  to schedule functions to be executed later. The ability to delay code execution gave us the ability to schedule our code to be run when we want. We can break it up into as little chunks as we want and feed it to the engine how we want.

console.log( 'h' );
console.log( 'e' );
console.log( 'l' );
console.log( 'l' );
console.log( 'o' );

// engine will run the code at once
// five console log commands will be processed at once

// output
// > h
// > e
// > l
// > l
// > o

setTimeout( function(){ console.log( 'h' ) }, 100 );
console.log( 'e' );
console.log( 'l' );
console.log( 'l' );
console.log( 'o' );

// engine will run the code at once
// one setTimeout and four console.log will be processed at once

// output
// > e
// > l
// > l
// > o

// --- 100ms later ---
// one console.log will be processed

// output
// > h

Q: How does this work?
A: When your code is first processed by the engine, setTimeout “sets an alarm” for when the engine should run the code you pass to it. Code execution completes, alarm goes off later and the engine finally runs setTimeout’s callback.

 

console.log( 'you and javascript' );
console.log( 'sitting in a tree' );

setTimeout( console.log, 3, 'then comes a baby' );
setTimeout( console.log, 3, 'in a baby carriage' );
setTimeout( console.log, 2, 'then comes marriage' );
setTimeout( console.log, 1, 'first comes love' );

// relevant output order

// > you and javascript
// > sitting in a tree
// > first comes love
// > then comes marriage
// > then comes a baby
// > in a baby carriage

It’s important you understand that your current code is processed in a different batch of code than setTimeout.

When you make code run in a different batch than code it was processed with, your code is now running out of sync.

The batch of code I’m referring to is the call stack. I’m already deep in the weeds now so I don’t want to get into it. I’m sorry for any confusion referring to it as batch of code creates.

async code is code that runs in a different batch of code than the one it was created in.

This brings us back to the first question.

Yield is the process of making code run in a later batch than when it is created. Yielded code runs in the future. Yielded code is asynchronous.

The ability to yield a function (run it in future), is very important because it opens up a very powerful pattern. You can write code that does something now and sets up to do more in future. You can break up your work over time.

var state = { x: 0, y: 0 };

// yielding console.log
setTimeout( function(){
 console.log( state );
}, 1000 );

state.x = 1;
state.z = 1;

// three instructions created and run in order
// setTimeout, update state.x and finally create state.z 

// --- 1000ms later ---

// setTimeout callback run
// one instruction created and run
// console.log

// output

// > { x: 1, y: 0, z: 1 };


We need to know about yield because it is the only way to deal with an interesting JavaScript feature.

Quick recap before we go forward, just to refresh our memory.

JavaScript
1. Your code [sync]
2. Runtime [???]
3. Engine [async]

In JavaScript …
1. Asynchronous (async) code performs better than synchronous (sync) code.

Glossary

Sync: code that runs in the same batch as the code that created it
Async: code that runs in a later batch than the code that created it

Runtime: some mysterious entity separate from the engine. Gives us setTimeout
Engine: Reads and runs our code, does pretty much everything. Is async.

Yield: run code asynchronously by using runtime apis to schedule when different batches of code run in the engine.

We’ve learned all the hard concepts. Everything from this point forward is really simple to understand.

One new things to keep in mind.

In JavaScript …
1. Asynchronous (async) code performs better than synchronous (sync) code.
2. When your code is running in the engine, every other else freezes.

Yes you read right. Every time your code runs, you freeze up the entire program!

setInterval( console.log, 100, 'stay awhile and listen' );
setTimeout( code_that_takes_5000ms_to_complete, 300 );

// output 

// --- 100ms later ---
// > stay awhile and listen

// --- 100ms later ---
// > stay awhile and listen

// --- 100ms later ---
// > stay awhile and listen

// setTimeout run
// running code_that_takes_5000ms_to_complete

// --- 5000ms later ---

// output

// > stay awhile and listen

You dishonor Deckard Cain when you freeze everything for so long. Also, you also get slow, janky JavaScript performance.

The trick to working with JavaScript is to minimize the amount of time your code runs. There is a common strategy.

1. Break your code up to run in very tiny chunks at a time.
2. Yield chunks to the engine one at a time.

Simple right?

To understand, sure.  To solve correctly? In an API that you love writing? In a way that is easy to follow and make sense? In a way that you have proper control over #2? (tee hee)

I fear this session must come to a close in a minute.

I have some more to share, but time and my meager finances will not permit me much longer than the hours I’ve already invested.

But first, one last look at the notes we’ve made so far.

JavaScript
1. Your code [sync]
2. Runtime [???]
3. Engine [async]

In JavaScript …
1. Asynchronous (async) code performs better than synchronous (sync) code.
2. When your code is running in the engine, every other else freezes.

#2 stinks (tee hee) because it typically degrades the performance of complex JavaScript apps.

We talked about how to solve it.

1. Break your code up to run in very tiny chunks at a time.
2. Yield chunks to the engine one at a time.

We need to yield the code chunks one at a time so that we take up as little time in our code as possible before yielding to the engine.

I’d like you to think carefully about these next questions next time you’re reading or writing JavaScript.

1. Is the code broken up into little chunks?
2. Are the chunks yielded individually to the JavaScript engine?
3. Is it done in an API you love reading / writing?
4. Is it easy to follow and make sense of?
5. Do you have proper control over how the chunk / yield process?

JavaScript, in my opinion, is limited primarily by the developer’s (in)ability to break up work into little chunks, yield the chunks to the engine individually, write code that makes it easy to follow how they do this while providing absolute control over the process.

– akamaozu
November 5, 2018