I have a CPU-heavy calculation function in my Node.js backend, and I expected it to run asynchronously because I wrapped it inside a Promise. However, when I call this function, the entire server becomes unresponsive for a moment. Other incoming requests slow down, and some are delayed until the calculation completes.

Here is a simplified example

function heavyTask() {
  // Simulating CPU-heavy work
  let count = 0;
  for (let i = 0; i < 1e9; i++) {
    count += i;
  }
  return count;
}

function runTaskAsync() {
  return new Promise((resolve) => {
    const result = heavyTask();   // expected this to be async
    resolve(result);
  });
}

app.get("/calculate", async (req, res) => {
  const result = await runTaskAsync();
  res.send({ result });
});

Even though runTaskAsync() returns a Promise, the CPU-intensive loop still blocks the event loop, and the server cannot handle other requests smoothly. I thought Promises made code asynchronous, so I’m confused about why this still blocks everything.

My questions

  1. Why does the event loop still get blocked even though I used a Promise?

  2. What is the correct way to run CPU-heavy work without blocking the main thread?

  3. Should I use Worker Threads, Child Processes, or something else?

Environment

  • Node.js v18

  • Express.js server

What is the proper approach to fix this?

4 Replies 4

The promise constructor does not create separate threads or otherwise make an operation parallel.

Why does the Promise object block rendering?

Is the Promise constructor callback executed asynchronously?

What is the proper approach to fix this?

Use a WebWorker.

Because the task is not asynchronous.

Promises are not inherently asynchronous. It is just a DESIGN PATTERN to handle asynchronous operations. It is the functions that Promises wrap that are asynchronous, not the Promise itself.

There are several ways to do asynchronous operations in node. Worker threads is one:

https://nodejs.org/docs/latest/api/worker_threads.html

Another is to use child_process to fork another script and communicate with it using events.

Because promises are asynchronous and not parallel.

Asynchronous means multiple things happening in the same time span. It means task switching inside this time span.

Parallel means multiple things happening at the same time.

For the most part the js runtime is single threaded with something called The Event Loop that schedules and switches between tasks on this single thread.

However, and here comes the big asterisk, inside a promise you can schedule and (a)wait for parallel work to happen. When you call async browser api like fetch then this work is done off the main js thread inside the browser engine. This is why you should use browser apis whenever you can instead of doing work in js code, as this frees up the main js thread. And you do have access to making more js threads. These are called web workers and for security purposes you can only interact with them through message passing.

First, we need to understand how event loops work. Your event loop is looping over events, getting the first event to be executed, executes it in the main thread and then proceeds with the next one until the event loop is cleared, when it will essentially wait for new events to process.

Now, if you have

  • event 1
  • event 2
  • ...

to process and you create a promise, then you create a new event to be handled, so your loop will look like this:

  • event 1
  • event 2
  • ...
  • newevent

Once your operation reaches newevent, it will be executed. If it's costly, then all subsequent events that were created will not be picked up until newevent's processing finishes. This is what you have seen.

Now, there are examples when the heavylifting is done by something else, that is, you send a request or something of the like. If your request is async, then after sending the request to a third-party and waiting for the response, your other events will be picked up and once the response arrives, the response handler will be picked up and executed.

However, if you have a busy event, then you basically have two possible solutions:

  1. You can break your large event to a chain of batches, adding an element to the event loop whenever the current batch finishes. So if you have for example 1 000 000 jobs to process, but 1 000 jobs are not too costly, then you could break it down to 1 000 events, each event adding an event to the loop.
  2. You can use worker threads to do the heavy-lifting, separating the costly operations from the main thread

Your Reply

By clicking “Post Your Reply”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.