Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

How do Async Javascript Programs Interact

Tags:

javascript

Back in the early days of web development, I picked up a bit of folk wisdom that, for code like this

<script src=".../program1.js"></script>
<script src=".../program2.js"></script>

the browser would pause, load the javascript, compile it, execute it, move on to the next script tag, and repeat. In this way, the browser would work its way through all the javascript on a page and treat it as one linear program.

However, in the brave new modern javascript world, we have asynchronous loading via the async attribute

<script src=".../program1.js" async></script>
<script src=".../program2.js" async></script>

It's my understanding that this is a good thing because now the browser does not need to pause, download the script, and execute it. Instead it starts downloading the script, but will continue to parse the DOM. i.e. the web page no longer blocks while waiting for javascript to download. (if this is not true I'd appreciate the correction).

However, what's less clear (and harder to test) is how these two programs interact. They appear to run in the same shared space (i.e. javascript is still, from a userland perspective, single threaded with two (global, function) scopes). However, what order they execute in seems ill-defined in the documentation I've read.

I've read through the MDN article on Concurrency model and Event Loop. While interesting and useful, it doesn't quite answer my question. From what I gather, when the browser loads program1.js or program2.js, javascript will add a message to the event queue, and that message will be processed as the javascript engine runs through the event loop.

What's missing for me is -- what does that message say? Is it a single message for each program that says "compile and execute all this javascript code"? Or does each program create multiple messages -- in my mind that might look something like

  • Message 1: Extract all the functions from this program and compile them
  • Message 2: Extract all statements and expressions in the global scope from this program
  • Message 3-n: Add each statement and expression as an individual queue message for later processing.

And what happens when the browser is in the middle of processing program1.js, but finishes downloading program2.js? Is it possible that execution of statements from each program will be interleaved?

I realize that, as a client developer, the best practice here is to not rely on global scope and write each program and function so it doesn't matter how it's called, and doesn't block other people's code. However, I spend a lot of time dealing with other people's code, some of which isn't well behaved. I'd like to understand what's going on behind the scenes, or if this is behavior that's undefined and, engine independent, and won't line up between implementations.

like image 425
Alan Storm Avatar asked Dec 15 '16 17:12

Alan Storm


People also ask

How does JavaScript async work?

An async function can contain an await expression, that pauses the execution of the function and waits for the passed Promise's resolution, and then resumes the async function's execution and returns the resolved value. You can think of a Promise in JavaScript as the equivalent of Java's Future or C# 's Task.

How asynchronous events are handled in JavaScript?

An async function can handle a promise called within it using the await operator. await can be used within an async function and will wait until a promise settles before executing the designated code. The await operators here ensure that the data is not logged before the request has populated it with data.

How is JavaScript async implemented?

JavaScript Async Functions Async and await are built on promises. The keyword “async” accompanies the function, indicating that it returns a promise. Within this function, the await keyword is applied to the promise being returned. The await keyword ensures that the function waits for the promise to resolve.

What makes JavaScript asynchronous?

Asynchronous JavaScript: Asynchronous code allows the program to be executed immediately where the synchronous code will block further execution of the remaining code until it finishes the current one.


Video Answer


2 Answers

There are two articles illustrate the "async" and "defer" properties in a practical sense (I'm green on the browser internals):

From 2014 with excellent + simple graphics: http://www.growingwiththeweb.com/2014/02/async-vs-defer-attributes.html

From 2016 why async might be an anti-pattern: http://calendar.perfplanet.com/2016/prefer-defer-over-async/

Async

To your question, what "async" instructs the browser to do is:

  1. Start downloading this resource as early as possible.
  2. Move on and feel free to download other resources in parallel.
  3. But as soon as my download completes, stop rendering and begin executing me as early as possible.

Effectively "async" can still block rendering and subsequent execution, but it allows the parser to continue working right up until execution can begin.

Multiple async scripts have no guarantee what order they will execute in. Depends how quickly the load. That lets AMD-systems like RequireJS define dependencies and callbacks to load resources async but queue their execution until the "global" environment contains all the prerequisites and the execution order can be negotiated (by rune magic) hopefully.

Defer

Defer behaves like this:

  1. Start downloading this resource as early as possible.
  2. Move on and feel free to download other resources in parallel.
  3. But as soon as my download completes, do nothing until the parser is done.
  4. Now execute all the scripts in the order they were discovered.

On one hand, "defer" is faster because it can never block the parser or rendering. But "defer" can be slower because it must wait to execute until the pipeline is clear.

That sounds like "async" is always better, but if you load 2 MB of JS on weak-CPU phone with a fast connection you may end up waiting 10s for execution before the parser is allowed to finish rendering. Using "defer" would prevent that at the expense your interaction layer being delayed.

The distinction is fuzzier if you're talking client/server or client-side apps. It may be more beneficial to use defer in a backend heavy app like Magento where rendering is handled server-side.

In a fully client-side app you probably get zero content until the JS monolith is loaded, so "defer" doesn't really do anything for you, but then neither does "async" if your whole app is a massive JS bundle and there's nothing to do in parallel.

like image 162
Brendan Falkowski Avatar answered Oct 20 '22 00:10

Brendan Falkowski


Is it possible that execution of statements from each program will be interleaved?

No, absolutely not. JS is still single-threaded, and one program runs after the other (although which of them comes first might be unknown).

What does that event loop message say?

The message is a ScriptEvaluationJob. Assuming parsing of the script is successful, it will instantiate all declarations and evaluate the script body, all in one run.

like image 27
Bergi Avatar answered Oct 20 '22 00:10

Bergi