3

Starting with a very simple example asking a user to confirm a choice:

let a = function(){ return window.confirm( "Proceed" ); };
let b = function(){ console.log("b: " + a() ); };
let c = function(){ b(); console.log( "after b" ); };
c();
c();

window.confirm() is an asynchronous event but the function behaves in a synchronous way and blocks execution until it has resolved.

Changing this to use a custom UI which uses await to block execution until the user responds. But the await documentation states:

The await operator is used to wait for a Promise. It can only be used inside an async function.

So the function containing the await expression has to be made async and returns a promise, not the value as before, and now a() does not block execution:

let a = async function(){
  let conf = document.createElement( "DIV" );
  conf.className = 'confirm';
  let message = document.createElement( "DIV" );
  message.className = 'confirm-message';
  message.innerText = 'Proceed';
  conf.appendChild( message );
  let ok = document.createElement( "BUTTON" );
  ok.innerText = 'Ok';
  conf.appendChild( ok );
  let cancel = document.createElement( "BUTTON" );
  cancel.innerText = 'Cancel';
  conf.appendChild( cancel );
  document.body.appendChild( conf );
  let v = await new Promise(
    response => {
      ok    .addEventListener( "click", ()=>{ response(true ); }, false );
      cancel.addEventListener( "click", ()=>{ response(false); }, false );
    }
  );
  conf.remove();
  return v;
};
let b = function(){ console.log("b: " + a() ); };
let c = function(){ b(); console.log( "after b" ); };
c();
c();
.confirm { display: grid; width: min-content; grid-template-columns: 10em 10em; grid-gap: 0.5em; grid-auto-rows: 1.4em; text-align: center; border: 1px #777 solid; background-color: #AAA; padding: 0.5em; }
.confirm > .confirm-message { grid-column: 1 / 3; }

This just moves the problem up the call stack and successive functions need to use await/async to ensure they wait until the user input has resolved:

let a = async function(){
  let conf = document.createElement( "DIV" );
  conf.className = 'confirm';
  let message = document.createElement( "DIV" );
  message.className = 'confirm-message';
  message.innerText = 'Proceed';
  conf.appendChild( message );
  let ok = document.createElement( "BUTTON" );
  ok.innerText = 'Ok';
  conf.appendChild( ok );
  let cancel = document.createElement( "BUTTON" );
  cancel.innerText = 'Cancel';
  conf.appendChild( cancel );
  document.body.appendChild( conf );
  let v = await new Promise(
    response => {
      ok    .addEventListener( "click", ()=>{ response(true ); }, false );
      cancel.addEventListener( "click", ()=>{ response(false); }, false );
    }
  );
  conf.remove();
  return v;
};
let b = async function(){ let v = await a(); console.log("b: " + v ); };
let c = async function(){ let v = await b(); console.log( "after b" ); };
(async function(){
  let c1 = await c();
  let c2 = await c();
})();
.confirm { display: grid; width: min-content; grid-template-columns: 10em 10em; grid-gap: 0.5em; grid-auto-rows: 1.4em; text-align: center; border: 1px #777 solid; background-color: #AAA; padding: 0.5em; }
.confirm > .confirm-message { grid-column: 1 / 3; }

Is there a way to use the new await syntax to block execution without changing the signature of all the calling functions and also making them async (to mimic the example using the built-in window.confirm() function)?

MT0
  • 143,790
  • 11
  • 59
  • 117
  • I didn't clearly understand what you are trying to ask/do regarding your question at the end? – Legends Sep 22 '17 at 20:43
  • In the first example, `window.confirm()` blocks execution until the user responds and does not require the function (or its calling function) to be `async` but in the last example, to make `a()` `await` the user input all the functions `a()`, `b()`, `c()` and the anonymous function they are called from need to be `async` otherwise they do not wait for the user input. Is there a way to use `await` without having to make all the functions in hierarchy that call it `async`? – MT0 Sep 22 '17 at 21:21
  • 3
    I guess no, async all the way up, that's how it should be. But what are you trying to achieve? – Legends Sep 22 '17 at 21:28
  • look here: https://stackoverflow.com/questions/43832490/is-it-possible-to-use-await-without-async-in-js – Legends Sep 22 '17 at 21:35
  • "what are you trying to achieve?" To block execution until the promise resolves and to not have to modify all the preceding function in the call stack. It may not be possible using promises but it is how `window.confirm()` functions and that is what I am mimicking (with a custom UI) in the final example. – MT0 Sep 22 '17 at 22:51
  • 1
    What I have understood is, that you have two dialogs/prompts. Block execution until the first dialog/prompt is resolved (Ok/Cancel), then show up the next dialog/prompt. If my assumption is right, you don't need async and await here, but you have to rewrite your code to only use promises. And based on the state of the first promise (dialog/prompt) --> resolved/rejected (state) to show the next dialog if the state is resolved. Or if the state is rejected continue with your desired business logic. – Legends Sep 24 '17 at 12:49
  • 2
    It looks like someone added a bounty to this so I can't vote to close, but besides the question linked by Legends, there are many other questions about using async/await from synchronous code. The answer is absolutely: no, you can't make async code synchronous somewhere up the chain. You can choose to use promise syntax instead of async/await; you could use a wrapper function to convert Promises to callbacks (`util.callbackify` or some similar thing); but whether it's using Promises or callbacks, asynchronous code is asynchronous and that will affect the entire code path. – Zac Anger May 08 '23 at 00:47
  • Does this answer your question? https://stackoverflow.com/a/45448272/1048572 – Bergi May 08 '23 at 01:10
  • 4
    It's like calling a one-person pizza place (where just one person works there - who answers the phone, makes the pizza even delivers them) to order a pizza and then refusing to hang up the phone after placing your order saying "no, I'll stay on the phone until the order gets here, just to be sure (synchronous)" Then the pizza shop person says "How do you expect me to make and deliver the pizza if I'm staying here on the phone with you?!?" – Wyck May 08 '23 at 02:09
  • 1
    @Andy "*that is not how it works*" - actually, that is how it does **not** work – Bergi May 10 '23 at 20:48
  • @Bergi that link is relevant and helpful. Sadly, JS really sucks and await/async has a catastrophic design defect. In that answer, while the suggestion does not change the function syntax, a separate `await` must be added to any place that the function was called. So not really a solution yet. I had the same drama as the OP. – Handsome Nerd May 12 '23 at 19:04
  • 1
    @HandsomeNerd tbh I really prefer JS' approach to concurrency, where `await` clearly marks the points where code execution can be interleaved, over anything where you have parallel threads or concurrent calls without any indication. – Bergi May 13 '23 at 02:08
  • `window.confirm() is an asynchronous event` -- **Wrong** window.confirm is synchronous. You know it's synchronous because it blocks not just javascript but also all other events like file downloads etc. – slebetman May 14 '23 at 06:39

3 Answers3

0

When an async function reaches an await statement, the caller of the function will continue to execute, if it does not use await to also pause its execution. This lets you control on which level your program will stop and wait.

Since you want it to stop on the top level, there is no way to avoid adding await/async to all functions along the call stack.

get_Achim
  • 11
  • 2
  • it's clear from the call stack that a function is called using await, so it knows to wait for no need to writhe async everywhere even on the functions that are not async by nature. adding async is not a requirement for code execution conceptually. – Handsome Nerd May 17 '23 at 00:59
0

If I understand correctly, you want to block execution in the top level/global context. Top level await is only available in modules. Unless you use modules, you cannot block execution in the global context (and even then, it's only blocked for that module).

The next best thing you can do is to create a sort of main async function in which you put all of the code that you would expect to block. Now if you put a(), b() and c() in this function, when using await all other code in this function will be blocked. The main function will return a Promise and run async, however, since all of your code is in this function, inside it, it‘s synchronous. Making the main function block the entire JavaScript thread is not possible. Do note that this requires re-writing your functions to use async/await.

This solution involves moving the problem as much as possible to the top of the call stack and that is the only solution if you want to block other code. Note that you also cannot block other visual events on the page, like focusing an input, clicking buttons, triggering CSS hover animations, etc. without using additional JS to cancel these events while your prompt is open or using some sort of overlay/backdrop.

Here are some examples (I have used ES features such as arrow functions, but if that specifically doesn't work for your project, feel free to change that):

(async () => {
    const a = () => {
        // … render HTML etc.
        return await Promise(resolve => { /* … add event listeners */}
    }
    const v = await a();
    console.log("value: ", v);
    console.log("end");
})();

(async () => {…})() is your main function, inside which you can simulate synchronous execution (if all of your code that you want to block is inside this function)

If you want to use b() and c(), similarly you can:

(async () => {
    const a = async () => {
        // … render HTML etc.
        return await Promise(resolve => { /* … add event listeners */}
    }
    const b = async () => {console.log("value: ", await a())};
    const c = async () => {await b(); console.log("end");
    await c();
    // any other after this line is blocked until resolution of `c()`
})();

Demo:

(async () => {
  const a = async () => {
    const conf = document.createElement('div');
    conf.className = 'confirm';
    
    const message = document.createElement('div');
    message.className = 'confirm-message';
    message.textContent = 'Proceed';
    conf.appendChild(message);
    
    const ok = document.createElement('button');
    ok.textContent = 'Ok';
    conf.appendChild(ok);
    
    const cancel = document.createElement('button');
    cancel.textContent = 'Cancel';
    conf.appendChild(cancel);
    document.body.appendChild(conf);
    
    const v = await new Promise(resolve => {
        ok.addEventListener('click', () => resolve(true), false);
        cancel.addEventListener('click', () => resolve(false), false);
    });
    conf.remove();
    return v;
  };
  const b = async () => console.log("b: " + await a());
  const c = async () => {
    await b();
    console.log( "after b" );
  };
  
  await c();
  // delay of 1 second
  await new Promise(resolve => setTimeout(resolve, 1000));
  console.log("2nd go");
  await c();
})();
.confirm { display: grid; width: min-content; grid-template-columns: 10em 10em; grid-gap: 0.5em; grid-auto-rows: 1.4em; text-align: center; border: 1px #777 solid; background-color: #AAA; padding: 0.5em; }
.confirm > .confirm-message { grid-column: 1 / 3; }

Another option would of course be to simply use a module. Third-party libraries like Webpack achieve top level await by placing all of your code in an async function, similarly to my demo.

To summarise:

Blocking the main or browser tab thread is NOT possible

in browser JavaScript. That would be extremely bad and allow you to "freeze" the user's tab, not even allowing them to close it. Blocking is possible with top level await in modules. To simulate blocking, you can put all of your code in an async context; in this context, any await will block the entire context.

In Node.js, top level await is supported in newer versions and you can block the global context of your Node.js app. Adding a wrapper like (async () => {…})(); is therefore not needed if you‘re using a supported version of Node.js.

undefined
  • 1,019
  • 12
  • 24
  • 1
    No, OP does not want block execution in the global context, and they already know how to use an AIIFE. They want "*to block execution without changing the signature of all the calling functions and also making them `async`*". – Bergi May 14 '23 at 15:07
  • @Bergi I see that several times OP mentioned "and now a() does not block execution" and "Is there a way to use the new await syntax to block execution". What my answer explains is that you cannot block execution as `window.confrm()` does. `window.confirm()` blocks the tab's thread. Making the functions `async` changes their signature. In my answer I have copied the functions from OPs question, with the only difference I have used ES syntax. When referring to blocking all other functions without changing them, I think OP is exactly referring to blocking the "main" thread like `prompt()` – undefined May 15 '23 at 05:13
-1

You should be using the then() function. Then returns the results of your async function after fulfilling the promise.

// First define your main async function. 
let a = async function(){
  let v = await new Promise(
    response => {
      ok    .addEventListener( "click", ()=>{ response(true ); }, false );
      cancel.addEventListener( "click", ()=>{ response(false); }, false );
    }
  );
 
  return v;
};

// Then get the results of your async function

let b = a.then((result) => { 
   console.log("b: " + result ); 
});

Benson
  • 4,181
  • 2
  • 26
  • 44
  • I don't see how that does not "*change the signature of all the calling functions*". OP does not want to modify their `b` and `c` functions (which you've conveniently omitted) – Bergi May 12 '23 at 05:25
  • My answer is a little controversial, it's had a few upvotes and a few downvotes. My answer is intended to answer the question's title, simply, "how to prevent adding await/async to all functions?" – Benson May 14 '23 at 00:10