Share:

October 16, 2023

Justin Kazmierczak

Making the Impossible Possible:

Making the Impossible Possible:

Share:

Published on October 16, 2023

Justin Kazmierczak

Threading Services in Node.JS.

Beautiful software that prioritizes user input and supports diverse systems depends on a software engineering practice called "Multithreading." "JavaScript doesn't do that - yet!" Okay, maybe that's too much of a definitive statement.

JavaScript is a powerful ECMAScript language that can write well-documented code that supports sophisticated data structures of your design. It's a fourth-generation language with several layers removed from machine code and doesn't give software architects low-level access. It's all classes, factories, and objects. No memory addressing is required.

Some History

For those who don't know, any browser in iOS is forced to use iOS Safari's version of the web-kit rendering engine, which is the same as opening it in iOS Safari. This means testing the mobile version of Chrome on iOS isn't very useful.

To the web designer of the early 2000s, Javascript was a literal pain point for clients. At that time, you suffered from various degrees of API adoption with vendors who built poorly documented browsers. The documentation for the internet was only easily accessible when real-time applications could be written in JavaScript (through an old powerful tool called Ajax). Open-source documentation and question-answering communities like StackOverflow, Mozilla docs, W3 Schools, and great forum communities sprang up all over the internet, offering solutions to browser-specific problems.

JavaScript was looking like a great solution to simplify UI/UIX relationships. However, it still has one fundamental problem: JavaScript has an event loop.

The Event Loop

Consider for a moment that you are driving on a freeway, happy to get that coffee before work, or looking forward to ending a third shift with family and friends. You are about to hop onto the highway when suddenly you are stopped. You're not going anywhere; you must wait for a car to enter and exit the freeway. One car at a time. The exit doesn't matter how many on-ramps or exits you have; only one car can enter the highway, and that same car must exit before another drives up. Your car number 200, you're waiting for car 1 to enter the freeway, drive, and exit. Car 2 to enter the freeway, drive, and exit. Car 3 to enter, drive, exit, and on and on it goes.

The phenomenon just described is called "Event Loop Blocking". It produces a bottleneck and a dreadful user experience.

I once developed an application using VB.net with the .net framework 1.0. The program was designed to process many check files at once, and it would convert the data to PCL5 (or something like that) and print the checks with a magnetic strip… one by one. A user would click a button to select their paycheck file outputted from an RPG or Cobal program and then another button to print the checks. It was not an efficient process.

My boss approached me about making it easier so a user could load multiple print files as the report system printed them out.

So, I added a list box, a drag and drop function and pushed play (build & test). I dropped the first file into the box. The app began processing the first file. I dropped the second file and discovered why this error message happens: "Application stopped responding." The interface locked up because it was in the middle of a process.

An application stopped responding image modified with the text: my brain has stopped working.

A user drops in a file but can not drop another file on the same thread (or process) because it is still processing the previous check file, causing the program to function abnormally. If you're an editor and you are familiar with Avid Media Composer's older versions, I need not say anything further.

JavaScript is built around an event loop and couldn't effectively provide multithreaded processing. The web has developed many solutions to this problem.

Web Worker Toil

The "web workers" method is a sophisticated solution to web background processes. Web workers enable "background threading," where a process gets dispatched and may report its status as it goes about its business. Think of rendering or exporting a video.

The video is being exported frame by frame, but you don't see the individual frames drawing (except for maybe Afet Effects). In some cases, those frames can all be multithreaded individually, processing a frame on a computer core. Yes, the more computer cores you have, the more processes you can run in parallel with one another.

Some developers enjoy using web workers; however, let's look for something more sophisticated. I wanted a true messaging and dispatch system that shared objects and classes, but this wasn't solving that solution.

A worker process is a self-contained event loop. You still have to wait for the worker process to do its thing before it will accept another process. It's that same problem, but at least the user can go their merry way thinking the process is finished - until they close the browser and realize the process, let's say a report, is still processing—the horror.

{"namespace":"ua.c.code","name":"code0","style":"height: 350px;","inner":"// Create a new web worker\r\nconst worker = new Worker('worker.js');\r\n\r\n// Listen for messages from the worker\r\nworker.onmessage = function (event) {\r\n console.log('Main Thread received a message from the Worker:', event.data);\r\n};\r\n\r\n// Send a message to the worker\r\nworker.postMessage('Hello from the main thread!');\r\n\r\n// The worker.js\r\n\r\n// Create a new web worker\r\nconst worker = new Worker('worker.js');\r\n\r\n// Listen for messages from the worker\r\nworker.onmessage = function (event) {\r\n console.log('Main Thread received a message from the Worker:', event.data);\r\n};\r\n\r\n// Send a message to the worker\r\nworker.postMessage('Hello from the main thread!');","type":"javascript"}

I promise not to reject it.

The JavaScript promise and rejection architecture inventors should probably get an award for inventing the most complicated and convoluted structure. It took me years to fully grasp the concept, and many programmers still go on chaining functions together without even knowing why.

Back to my car analogy, with a promise, you could get onto the freeway, but you still have to wait for the cars ahead of you to leave before you can go to any exit. One car can still exit at a time, but you can happily enter the freeway and wait. This is opposed to my earlier example when one car could enter the highway simultaneously. However, you aren't moving; you're in the queue.

A promise adds a request to do something (usually a function call) to a job queue. The job queue is an "in the order requests are received" process, but a new car can enter the freeway and not crash the entire system. It would be best to supply all the data a promise needs at the call time or at least give it a proper scoping. It can access shared resources but may not resolve or return data immediately.

A promise function returns a promise - that, at some unknown point in time, it may resolve and get a response. That response could be anything; at the very least, it should be something or an uncaught error.

The programmer is stuck with a dilemma: he must modify the interface to appear like something is happening while the promise resolves itself. If anything relies on the value the promise should return, the chain must include the function to proceed. It's called a thanable. Code reviewers would prefer to quit rather than review any code with promise chains.

{"namespace":"ua.c.code","name":"code1","style":"height: 350px;","inner":"//An example thenable chain\r\nfetchDataFromApi1()\r\n .then((data1) => fetchDataFromApi2()\r\n .then((data2) => performTransformation(data2)\r\n .then((transformedData) => logData(transformedData))\r\n .catch((error) => console.error(error))\r\n )\r\n .catch((error) => console.error(error))\r\n )\r\n .catch((error) => console.error(error));","type":"javascript"}

The example above shows a less complicated chain, but historically, these types of chaining can cause issues with adding features, as messing with an item in the chain can cause some point of the chain to break.

What's worse, if a promise rejection occurs and if you do not utilize an error handler with a .catch(), you'll receive a detached promise rejection error, that's if you're lucky! The system usually operates in blissful ignorance regardless of the error.

It solved the problem, but it in itself became a problem. JQuery helped a lot of users work with promises by simplifying the code. However, it didn't entirely stop the code reviewers from calling their therapists after a heated discussion with their programmer. I'd divulge their name, but they are currently in the JavaScript Promise Rejection Witness Protection Program.

Await for me, Asynchronous programming

When threading became a really important system archetype, it was quickly becoming problematic to write code. Code, for the most part, relies on input and provides a consistent output. Input, followed by a bunch of steps, produces your output, and that is the hallmark of a function. Programming is procedural in nature in that it processes a process, but that process can now be fired by any event or invoked by any object, thus resulting in event-oriented programming and object-oriented programming.

With an asynchronous function, it acts like a promise (it probably is a promise), but it's wrapped in a neat little toolkit. Plus, it feels very procedural, so it's easy to track what is going on in the code. Instead of chaining a bunch of .thenables, you can run this code.

{"namespace":"ua.c.code","name":"code2","style":"height: 350px;","inner":"// Cleaner Async/Await with Error Handling\r\ntry {\r\n const data1 = await fetchDataFromApi1();\r\n const data2 = await fetchDataFromApi2();\r\n const transformedData = await performTransformation(data2);\r\n logData(transformedData);\r\n} catch (error) {\r\n console.error(error);\r\n}\r\n\r\n//To await you must be in an async function or in the main thread\r\nasync function logData(result) {\r\n console.log(result.transformedData);\r\n}","type":"javascript"}

Clean procedural code.

The problem here is it is still bound by the asynchronous promise "job queue". Item can join the party but can't leave until the items above him in the queue have finished.

@jumpcutking/Threads

I developed an ingenious solution to this problem as part of a hardware investigation, and with the desire to rapidly deploy fixes, I started to Tincher with the ability to spawn processes from node.

Typically, it's an insecure feature, so lockdown what scripts are spawned and prevent code injection. However, if you have complete control of the scripts and your environment, Threads is a great tool.

In Threads, you can asynchronously wait for a process to run a command, and it will respond. You can also use a more webworker code structure. Threads will do the "ID" bookkeeping of registered events/actions for you. This enables you to code a complex system without breaking the event loop or over-exhausting your primary thread's job queue. It communicates safe JSON data objects between the threads and the manager, and since it's node, you can share the same functions and classes - you have to reinitialize them for each node.

I use these Threads when building a Universe App to compile SCSS stylesheets, web pack a client's version of UATools, reboot the webserver, and more. I also use threads in development to spool a Universe App and quickly restart it. It makes developing and testing Universe Apps much more manageable than your traditional setup.

I've also deployed threads as part of The Universe's App Test Manager, which runs to test functions and objects to ensure the expected input produces the desired result. I also use threads in Chaos Engineering to break the system and discover what flaws I can fix and what failpoints I need to plan to prevent.

Open-Source

@jumpcutking/Threads can talk to hardware directly, process reports in batches, run automation workflows, and more.

It's intuitively simple and open-source. It's a fraction of the Universe App Tools, so I broke it off to return something to JavaScript server developers. I don't want you to experience a world where you can't control an object's life cycle because of a broken promise, nor a product life cycle killed because feature creep has obliterated the event loop and the job queue.

It looks something like this:

{"namespace":"ua.c.code","name":"code3","style":"height: 350px;","inner":"/** test a requestAwait */\r\nasync function TestAwait() {\r\n\r\n console.log(\"Testing Deferred Promise... (await)\");\r\n \r\n try {\r\n \r\n //get the response from the thread\r\n var r = await threads.async(\"mythread.awaitResponse\", {\r\n hello: \"universe\"\r\n });\r\n\r\n console.log(\"Promise (await) Resolved!\", r);\r\n\r\n } catch (error) {\r\n\r\n // console.warn(threads.getLastUncaughtException());\r\n\r\n console.error(\"Promise (await) Rejected!\", error);\r\n \r\n }\r\n\r\n} \r\n","type":"javascript"}

Since it's an async/await model, plus an event dispatcher, you can safely use threads to speed up the performance of your server-side apps in connection to your user's experience of your app.

It's free and MIT-licensed. I have added a small provision to prevent abuse. I do believe in using source code responsibly.

A screenshot of the preview for the GitHub repository: @jumpcutking/threads.

Fork the code on GitHub.