1

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.

  • In the browser? I’d use web workers. In node? I’d use child processes. I’m pretty sure Javascript is inherently single threaded. – evolutionxbox Jul 14 '19 at 16:43
  • What kind of computation are you doing with what kind of data? What makes you think serialisation if inefficient, how big is the data? – Bergi Jul 14 '19 at 16:52
  • kabanus, don't believe that is a duplicate. Blocking and being asynchronous are not the same issue. Though albeit related, as typically one gets a result from non-blocking code an asynchronous manner. –  Jul 14 '19 at 16:57
  • Bergi, I have a kind of graphics application where a display list is created from a model. The model is sent from the server to the client side. I would like to add a shell layer to this. It would be a lot like the debug console one finds in Firefox or Chrome, but taylored for the domain of the graphics model rather than that of the html DOM. .. but I can think of many many application in real time response UIs where multithreading is advisable. You can get a bit of that through events here, but functions can not be prempted or restarted as far as I can see so I can't make a scheduler –  Jul 14 '19 at 17:00
  • Solid argument, so I suggest you re-word your question. The promise thing is taking way too much space for being comment on what you do not want. Put it in the end, after a separator or something. – kabanus Jul 14 '19 at 17:03
  • 1
    *"...to be run, while the calling thread does not block"*: the execution of the Promise constructor callback is executed by that same thread. There is no fork happening. – trincot Jul 14 '19 at 17:03
  • Good point, the ES jargon is something Im still getting my head around. –  Jul 14 '19 at 17:04
  • You are already getting answers on why promises do not block (including my duplicate mark), instead of how to make something not block - I suggest you shrink that part quickly. – kabanus Jul 14 '19 at 17:06
  • kabanus, I edited to deemphasize the promises as you suggested. However you are incorrect as the answer just below says that indeed promises are single threaded (blocking) and "promises just organize callbacks". That is good information I accepted that answer. –  Jul 14 '19 at 17:12
  • 1
    @user244488 yes, there is no preemption, you need to write cooperative code - e.g. by breaking your processing into multiple chunks that are processed asynchronously and checking whether to stop between them. – Bergi Jul 14 '19 at 18:14
  • Yes thank's Bergi, I'm getting the idea after reading the links in kroltan's post. I'll have to break the shell code up into pieces that get scheduled. –  Jul 14 '19 at 18:28

3 Answers3

5

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.

  • Web Workers would be perfect if I could pass a reference to a shared structure. In this ap we don't need access to the DOM from the webworker. Let me read up on the links you sent. Thank you very much for the reply.. –  Jul 14 '19 at 17:14
  • 1
    @user244488 Since JS is single-threaded, you can't share a structure. There are a few kinds of data that you can send and receive, but it is always copied, so you can't rely on referential equality of sent data. – This company is turning evil. Jul 14 '19 at 17:18
  • 1
    Kroitan those links were perfect. Enjoyed listening to Jake ... The situation could be better .. dataflow reservation queues for a single processor, with deadlock scenarios ... but now that I now that I can get this code done. –  Jul 14 '19 at 18:14
  • 2
    @kroitan not quite, there are SharedArrayBuffers which can be shared between different execution environments. – Jonas Wilms Jul 14 '19 at 20:09
  • web workers look to be separate processes, one is essentially running a daemon and communicating with it. I wonder if they are implemented that way. It makes sense that it would be possible to share a block of raw memory, SharedArrayBuffers, as that is typically supported for multiprocessing. Though then, they should also support some sort of mutex locks so we can synchronize over it. –  Jul 16 '19 at 09:39
0

I think you got some mistaken.

  1. What you ask for is multi-thread, not async.
  2. Async is not about multi-threading, async can run on single thread.

In your case, when you can then, you thread is freed, so it will invoke immediately and wait for the result, but if you need to do other things like render, decoding, etc... Show user the loading page! That's the purpose of async.

If you need to pre-do something. For example: pre-decoding some sort of data, pre-compute image, etc... Yes, I mean you must pre do it! Or use webworker. The problem is you must know the data and precompute it before showing its to the user.

More about async is here

Liem Le
  • 581
  • 7
  • 17
-2

PromiseConstructor invokes one argument is called executor, it must be a function and is executed synchronously with two argument resolve and reject.

So, in your code, the executor (wait_loop_resolve) => { this.wait_loop(wait_loop_resolve); } is executed immediately, which means your function wait_loop is executed also. You can use setTimeout(wait_loop, 0) or Promise.resolve().then(wait_loop) to execute it asynchronously. If it need to pass argument, you can write it in the following formats:

setTimeout(() => wait_loop(wait_loop_resolve), 0)

// or with promise
Promise.resolve(wait_loop_resolve).then(wait_loop)
acrazing
  • 1,955
  • 16
  • 24