In Javascript running in a modern browser, I want to have a non-blocking call to a function.
I was pointed to Promises, and async functions that are built upon the top of them, but have discovered that Promises (and hence async functions) do block. See the code below that demonstrates this.
The only functions that do no block appear to be built in. E.g. setTimeout. This appears to be why all of the 'nonblocking' examples of Promises I've found use setTimeout. The single thread of execution walks through the time out code and calls the nonblocking setTimeout so keeps on stepping. The Promise itself is just organizing the callbacks, which is good, but they themselves are not causing calls to be non blocking.
The webworker solution doesn't appear to take a reference and thus can not modify the caller's data. Serializing, calling a web worker, then unserializing to get the result back would be rather inefficient, and complicated.
Here is a typical example of a getting 'non-blocking' behavior from a Promise, but if you single step through it in the web console, you will see the only thing that doesn't block is setTimeout.
<!DOCTYPE html>
<head>
<meta charset="UTF-8">
</head>
<body>
<script>
'use strict';
function wait_setTimeout(call_when_finished){
console.log("entering wait_setTimeout");
setTimeout(call_when_finished, 2000); // 3 second delay
}
function wait_setTimeout_resolve(){
console.log("wait_setTimeout_resolved");
}
console.log("before wait_setTimeout promise");
let p0 = new Promise((wait_setTimeout_resolve) => {
this.wait_setTimeout(wait_setTimeout_resolve);
});
console.log("after new Promise(wait_setTimeout_resolve)");
p0.then(() => console.log("then wait_setTimeout_promise target"));
console.log("after wait_setTimeout_promise.then");
/*
before wait_setTimeout promise
entering wait_setTimeout
after new Promise(wait_setTimeout_resolve)
after wait_setTimeout_promise.then
<delay occurs here, as it should, but because setTimeout didn't block, not the Promise>
then wait_setTimeout_promise target
*/
</script>
</body>
If instead we create the time delay with a loop it is clear that the Promise is blocking:
<!DOCTYPE html>
<head>
<meta charset="UTF-8">
</head>
<body>
<script>
'use strict';
let limit = 100000; // ~3 second delay, make this bigger if you don't see a delay
function wait_loop(call_when_finished){
console.log("entering wait_loop");
let i = 0;
let j;
while(i < limit){
i++;
j = 0;
while(j < limit){
j++;
}}
call_when_finished();
}
function wait_loop_resolve(){
console.log("wait_loop_resolved");
}
console.log("before wait_loop promise");
let p1 = new Promise((wait_loop_resolve) => {
this.wait_loop(wait_loop_resolve);
});
console.log("after new Promise(wait_loop_resolve)");
p1.then(() => console.log("then wait_loop_promise target"));
console.log("after wait_loop_promise.then");
/*
before wait_loop promise
entering wait_loop
<delay occurs here.. i.e. the new Promise blocked>
after new Promise(wait_loop_resolve)
after wait_loop_promise.then
then wait_loop_promise target
*/
</script>
</body>
Of course the loop is just a placeholder. The actual code is computing something that is needed.
Promises are not threading, JS is very much single-threaded, and works off a event queue. Indeed promises just organize the callbacks.
If you want to run CPU-intensive code, then you'll need to use Web Workers, and communicate with them using their interface, postMessage
(which you can wrap into a Promise-returning format if you prefer). These do work as scripts running on separate threads, but note how their communication is restricted, it's not free-reign memory access like classic multi threading.
Web Workers will not have access to your window, so no DOM modifications in them. But if you have complex simulations you can smartly separate the data model from the display, and just transfer the data model to and from the worker, with a normal script translating that into the UI.
To learn more about JavaScript's execution model, I refer you to the introduction on MDN, and if you want a very deep view on how that is implemented, this presentation by Jake Archibald.