10

I wrote a simple function that returns Promise so should be non-blocking (in my opinion). Unfortunately, the program looks like it stops waiting for the Promise to finish. I am not sure what can be wrong here.

function longRunningFunc(val, mod) {
    return new Promise((resolve, reject) => {
        sum = 0;
        for (var i = 0; i < 100000; i++) {
            for (var j = 0; j < val; j++) {
                sum += i + j % mod
            }
        }
        resolve(sum)
    })
}

console.log("before")
longRunningFunc(1000, 3).then((res) => {
    console.log("Result: " + res)
})
console.log("after")

The output looks like expected:

before     // delay before printing below lines
after
Result: 5000049900000

But the program waits before printing second and third lines. Can you explain what should be the proper way to get "before" and "after" printed first and then (after some time) the result?

3
  • Well, if you wanted to truely test "after some time", you could put a setTimeout around your resolve(sum) statement. Commented Dec 20, 2018 at 21:37
  • 2
    This isn't going to work. You only get one thread for your code. Wrapping synchronous code in a promise or timeout doesn't change that. If you want to write asynchronous code you'll need to create a child process Commented Dec 20, 2018 at 21:37
  • 1
    Related: For client-side, there is also the Web Worker API for creating separate threaded tasks. Commented Dec 20, 2018 at 22:32

3 Answers 3

20

Wrapping code in a promise (like you've done) does not make it non-blocking. The Promise executor function (the callback you pass to new Promise(fn) is called synchronously and will block which is why you see the delay in getting output.

In fact, there is no way to create your own plain Javascript code (like what you have) that is non-blocking except putting it into a child process, using a WorkerThread, using some third party library that creates new threads of Javascript or using the new experimental node.js APIs for threads. Regular node.js runs your Javascript as blocking and single threaded, whether it's wrapped in a promise or not.

You can use things like setTimeout() to change "when" your code runs, but whenever it runs, it will still be blocking (once it starts executing nothing else can run until it's done). Asynchronous operations in the node.js library all use some form of underlying native code that allows them to be asynchronous (or they just use other node.js asynchronous APIs that themselves use native code implementations).

But the program waits before printing second and third lines. Can you explain what should be the proper way to get "before" and "after" printed first and then (after some time) the result?

As I said above, wrapping things in promise executor function doesn't make them asynchronous. If you want to "shift" the timing of when things run (thought they are still synchronous), you can use a setTimeout(), but that's not really making anything non-blocking, it just makes it run later (still blocking when it runs).

So, you could do this:

function longRunningFunc(val, mod) {
    return new Promise((resolve, reject) => {
        setTimeout(() => {
            sum = 0;
            for (var i = 0; i < 100000; i++) {
                for (var j = 0; j < val; j++) {
                    sum += i + j % mod
                }
            }
            resolve(sum)
        }, 10);
    })
}

That would reschedule the time consuming for loop to run later and might "appear" to be non-blocking, but it actually still blocks - it just runs later. To make it truly non-blocking, you'd have to use one of the techniques mentioned earlier to get it out of the main Javascript thread.

Ways to create actual non-blocking code in node.js:

  1. Run it in a separate child process and get an asynchronous notification when it's done.
  2. Use the new experimental Worker Threads in node.js v11
  3. Write your own native code add-on to node.js and use libuv threads or OS level threads in your implementation (or other OS level asynchronous tools).
  4. Build on top of previously existing asynchronous APIs and have none of your own code that takes very long in the main thread.
Sign up to request clarification or add additional context in comments.

1 Comment

For OS level threads in C++ addons, libuv has been pretty good in my experience. Here's a basic example I found. I have personally used it for doing image processing in realtime to do object detection in a non-blocking thread and then Node.js distributed the detected centroids to connected TCP clients (which used the data to actuate motors to move towards the detected object).
3

The executor function of a promise is run synchronously, and this is why your code blocks the main thread of execution.

In order to not block the main thread of execution, you need to periodically and cooperatively yield control while the long running task is performed. In effect, you need to split the task into subtasks, and then coordinate the running of subtasks on new ticks of the event loop. In this way you give other tasks (like rendering and responding to user input) the opportunity to run.

You can either write your own async loop using the promise API, or you can use an async function. Async functions enable the suspension and resumation of functions (reentrancy) and hide most of the complexity from you.

The following code uses setTimeout to move subtasks onto new event loop ticks. Of course, this could be generalised, and batching could be used to find a balance between progress through the task and UI responsiveness; the batch size in this solution is only 1, and so progress is slow.

Finally: the real solution to this kind of problem is probably a Worker.

const $ = document.querySelector.bind(document)
const BIG_NUMBER = 1000
let count = 0

// Note that this could also use requestIdleCallback or requestAnimationFrame
const tick = (fn) => new Promise((resolve) => setTimeout(() => resolve(fn), 5))

async function longRunningTask(){
    while (count++ < BIG_NUMBER) await tick()
    console.log(`A big number of loops done.`)
}

console.log(`*** STARTING ***`)
longRunningTask().then(() => console.log(`*** COMPLETED ***`))
$('button').onclick = () => $('#output').innerHTML += `Current count is: ${count}<br/>`
* {
  font-size: 16pt;
  color: gray;
  padding: 15px;
}
<button>Click me to see that the UI is still responsive.</button>
<div id="output"></div>

Comments

0

As explained by @jfriend00, the executor function fn in new Promise(fn) does not get queued into the microtask's promise queue. To really understand this sentence I recommend watching this video series on Nodejs' Event Loop. The Event Loop is treated from episodes 42 - 48 but I recommend to also watch the preceding episodes. The microtask queue mentioned there seems to be part of v8 and not of libuv's Event Loop which is why the following works for Nodejs as well as for browser JS.

Only the functions passed to the subsequent then statements get queued into the microtask's promise queue. So, we can instantiate a Promise and resolve it immediately and put the long computation of the sum into the following then.

function longRunningFunc(val, mod) {
    return new Promise((resolve, reject) => {
        // Inside promise's executor function
        // Synchronously executed from v8's callstack
        // Not queued into the microtask's promise queue!
        console.log("Inside Promise's executor function")
        resolve()
    }).then(() => {
        // Queued into the microtask's promise queue!
        // Thus, logged some time after "after"
        // Executed on v8's callstack as soon as popped from microtask's promise queue
        console.log("Start computing long sum")
        sum = 0;
        for (var i = 0; i < 100000; i++) {
            for (var j = 0; j < val; j++) {
                sum += i + j % mod
            }
        }
        console.log("Finished computing long sum")
        return sum
    })
}

console.log("before")
longRunningFunc(1000, 3).then((res) => {
    console.log("Result: " + res)
})
console.log("after")

Output:

before
Inside Promise's executor function
after
Start computing long sum
[ ... some delay ... ]
Finished computing long sum
Result: 5000049900000

Remark: A shorter way to achieve the immediate resolution of the Promise is by using Promise.resolve()

function longRunningFunc(val, mod) {
    return Promise.resolve().then(() => {
        console.log("Start computing long sum")
        sum = 0;
        for (var i = 0; i < 100000; i++) {
            for (var j = 0; j < val; j++) {
                sum += i + j % mod
            }
        }
        console.log("Finished computing long sum")
        return sum
    })
}

Comments

Your Answer

By clicking “Post Your Answer”, 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.