DEV Community

Cover image for Unjam your server: NodeJS collaborative multitasking
Mike Talbot ⭐
Mike Talbot ⭐

Posted on

Unjam your server: NodeJS collaborative multitasking

TL;DR

  • Sometimes we need to write code in Node that does significant processing
  • Often we can and should split this out into a separate process, but it isn't always practical
  • If the main thread is busy then your server is totally unresponsive, even for simple tasks
  • js-coroutines now allows you to perform collaborative multitasking in Node so that one or two long-running operations will not stop the server being interactive.
  • js-coroutines is available on MIT license and works on both front and back end JavaScript and TypeScript projects

Node processing

One place where we might need to do significant processing in Node is when creating a cache of information or updating that on a regular basis. As data volumes increase this processing can become very onerous and while it's happening the server is jammed up and not responding to other requests.

Even a rapid 5ms request will be blocked for potentially many seconds while the current workload is completed.

Here's an example, when you click the Run Big Job button, the time is no longer updating because the request is blocked on the main thread.

The example is firing a request for the server time every time it gets a result. If you click the Run Big Job button then the server will do a process involving copying and sorting a million records.

// This is the culprit 


let test = Array.from({ length: 1000000 }, () => Math.random() * 1000)

app.get("/bigjob", async (req, res) => {
    let copy = [...test]
    copy.sort()
    res.status(200).send("done")
})
Enter fullscreen mode Exit fullscreen mode

Coroutines

Using coroutines we can split up heavy jobs over time using collaborative multitasking. js-coroutines comes with a bunch of out of the box functions and you can write your own using generator syntax.

In this case we can rewrite our code above like so:

const { appendAsync, sortAsync } = require("js-coroutines")

let test = Array.from({ length: 1000000 }, () => Math.random() * 1000)

app.get("/bigjob", async (req, res) => {
    let copy = await appendAsync([], test)
    await sortAsync(copy)
    res.status(200).send("done")
})
Enter fullscreen mode Exit fullscreen mode

When you click the button in the example below, the time carries on updating. You can even click it more than once and the results will come back when they are ready (adding a * to the button text).

It's important to note that all of these operations are using a single thread, so it should not be used where there are multiple processors available to offload work for maximum performance, it is very useful when you don't want operations to block the main thread and they aren't processing time critical.

Node Support

Node support is enabled immediately, jobs are allowed up to 20ms to run, and then the event loop is executed.

Alt Text

Top comments (3)

Collapse
 
somedood profile image
Basti Ortiz

Quite unrelated, but...

Today I Learned: Array.from can also take in a mapper function, thereby emulating some sort of a "list comprehension" like in Python.

Collapse
 
jwp profile image
John Peters • Edited

I think I get it Mike, just like Task Agnostic CPU assignment in .NET, you are saying this is the same thing in js-coroutines. I always thought that when node actually would be able to spin off CPU agnostic tasks like .NET, it would truly be on par with .NET.

Collapse
 
miketalbot profile image
Mike Talbot ⭐

Yeah it would be better to properly run Worker as simply, in this case it's more akin to old fashioned Windows with things giving up the processor so something else can run :)