I'd like to accomplish the following using promises: only execute further once the state of something is ready. I.e. like polling for an external state-change.

I've tried using promises and async-await but am not getting the desired outcome. What am I doing wrong here, and how do I fix it?

The MDN docs have something similar but their settimeout is called within the promise--that's not exactly what I'm looking for though.

I expect the console.log to show "This function is now good to go!" after 5 seconds, but instead execution seems to stop after calling await promiseForState();

var state = false;

function stateReady (){
 state = true;
}


function promiseForState(){
 var msg = "good to go!";
 var promise = new Promise(function (resolve,reject){
        if (state){
            resolve(msg);
        }
    });
  
  return promise;
}

async function waiting (intro){
 
    var result = await promiseForState();
    console.log(intro + result)
  
}
setTimeout(stateReady,5000);
waiting("This function is now ");

4 Answers

1
jib On

What you're doing wrong is the promise constructor executor function executes immediately when the promise is created, and then never again. At that point, state is false, so nothing happens.

Promises (and async/await) are not a replacement for polling. You still need to poll somewhere.

The good news: async functions make it easy to do conditional code with loops and promises.

But don't put code inside promise constructor executor functions, because of their poor error handling characteristics. They are meant to wrap legacy code.

Instead, try this:

var state = false;

function stateReady() {
 state = true;
}

const wait = ms => new Promise(resolve => setTimeout(resolve, ms));

async function promiseForState() {
  while (!state) {
    await wait(1000);
  }
  return "good to go!";
}

async function waiting(intro) {
    var result = await promiseForState();
    console.log(intro + result)
}
setTimeout(stateReady,5000);
waiting("This function is now ");

0
Evert On

The way I would solve this, is as follows. I am not 100% certain this solves your problem, but the assumption here is that you have control over stateReady().

let state = false;
let stateResolver;

const statePromise = new Promise( (res, rej) => {

  stateResolver = res;

});


function stateReady(){
 state = true;
 stateResolver();
}


async function promiseForState(){
   await stateResolver();
   const msg = "good to go!";
   return msg;
}

async function waiting (intro){

    const result = await promiseForState();
    console.log(intro + result)

}
setTimeout(stateReady,5000);
waiting("This function is now ");

Some key points:

  1. The way this is written currently is that the 'state' can only transition to true once. If you want to allow this to be fired many times, some of those const will need to be let and the promise needs to be re-created.
  2. I created the promise once, globally and always return the same one because it's really just one event that every caller subscribes to.
  3. I needed a stateResolver variable to lift the res argument out of the promise constructor into the global scope.
0
Randy Casburn On

Here is an alternative using .requestAnimationFrame().

It provides a clean interface that is simple to understand.

var serverStuffComplete = false
// mock the server delay of 5 seconds
setTimeout(()=>serverStuffComplete = true, 5000);

// continue until serverStuffComplete is true
function waitForServer(now) {
  if (serverStuffComplete) {
    doSomethingElse();
  } else {
    // place this request on the next tick
    requestAnimationFrame(waitForServer);
  }
}
console.log("Waiting for server...");
// starts the process off
requestAnimationFrame(waitForServer);

//resolve the promise or whatever
function doSomethingElse() {
  console.log('Done baby!');
}

0
slebetman On

Based on your comments that you are waiting for messages from a server it appears you are trying to solve an X/Y problem. I am therefore going to answer the question of "how do I wait for server messages" instead of waiting for global variable to change.

If your network API accepts a callback

Plenty of networking API such as XMLHttpRequest and node's Http.request() are callback based. If the API you are using is callback or event based then you can do something like this:

function myFunctionToFetchFromServer () {
    // example is jQuery's ajax but it can easily be replaced with other API
    return new Promise(function (resolve, reject) {
        $.ajax('http://some.server/somewhere', {
            success: resolve,
            error: reject
        });
    });
}

async function waiting (intro){   
    var result = await myFunctionToFetchFromServer();
    console.log(intro + result);
}

If your network API is promise based

If on the other hand you are using a more modern promise based networking API such as fetch() you can simply await the promise:

function myFunctionToFetchFromServer () {
    return fetch('http://some.server/somewhere');
}

async function waiting (intro){   
    var result = await myFunctionToFetchFromServer();
    console.log(intro + result);
}

Decoupling network access from your event handler

Note that the following are only my opinion but it is also the normal standard practice in the javascript community:

In either case above, once you have a promise it is possible to decouple your network API form your waiting() event handler. You just need to save the promise somewhere else. Evert's answer shows one way you can do this.

However, in my not-so-humble opinion, you should not do this. In projects of significant size this leads to difficulty in tracing the source of where the state change comes form. This is what we did in the 90s and early 2000s with javascript. We had a lot of events in our code like onChange and onReady or onData instead of callbacks passed as function parameters. The result was that sometimes it takes you a long time to figure out what code is triggering what event.

Callback parameters and promises forces the event generator to be in the same place in the code as the event consumer:

let this_variable_consumes_result_of_a_promise = await generate_a_promise();

this_function_generate_async_event((consume_async_result) => { /* ... */ });

From the wording of your question you seem to be wanting to do this instead;

..somewhere in your code:

this_function_generate_async_event(() => { set_global_state() });

..somewhere else in your code:

let this_variable_consumes_result_of_a_promise = await global_state();

I would consider this an anti-pattern.

Calling asynchronous functions in class constructors

This is not only an anti-pattern but an impossibility (as you've no doubt discovered when you find that you cannot return the asynchronous result).

There are however design patterns that can work around this. The following is an example of exposing a database connection that is created asynchronously:

class MyClass {
    constructor () {
        // constructor logic
    }

    db () {
        if (this.connection) {
            return Promise.resolve(this.connection);
        }
        else {
            return new Promise (function (resolve, reject) {
                createDbConnection(function (error, conn) {
                    if (error) {
                        reject(error);
                    }
                    else {
                        this.connection = conn; // cache the connection
                        resolve(this.connection);
                    }
                });
            });
        }
    }
}

Usage:

const myObj = new MyClass();

async function waiting (intro){   
    const db = await myObj.db();
    db.doSomething(); // you can now use the database connection.
}

You can read more about asynchronous constructors from my answer to this other question: Async/Await Class Constructor