1

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 ");
  • State change in what? Local state? State on another server somewhere? The key is can you implement a promise that will resolve when the state changes? Or would you have to end up polling the state to do that anyway? We'd need to know more about the actual state change in order to help much. – jfriend00 Jun 02 '19 at 00:47
  • @jfriend00 local state. Basically, when a message from a server has been received, then I would want my promise to be resolved. – pleasedontcallmethat Jun 02 '19 at 00:51
  • Then, in some event handler for receiving the message, resolve the promise and use `.then()` on the promise to monitor when it's resolved. No polling required. Again, if you show REAL code instead of PSEUDO code, people can help you much more accurately and specifically. That is nearly always the case here on stackoverflow. Without that, we have to provide general answers and often attempt guesses at what your code is really doing. – jfriend00 Jun 02 '19 at 01:45
  • Changing a global state variable when you receive messages form a server is an anti pattern. Instead you should execute your code / resolve your promise when you receive the message. – slebetman Jun 02 '19 at 03:26
  • Sorry about the question lacking context. @jfriend00 and slebetman If it helps clarify, I do have an event handler for receiving the message from the server, but it's in a different scope, it's within the constructor of the function that creates my server connection. If it's there where I resolve the promise, would I need to declare my promise globally? – pleasedontcallmethat Jun 02 '19 at 03:49
  • Usually, the way you surface some event like that is you create some other event that is accessible at a higher scope and allow the the promise code itself to subscribe to the desired event. So, rather than make the promise global and hard code it into the event handler, you create an event that the promise can subscribe to. The exact details of how to do that depend upon your code is structured into modules. Ideally, you would import something into the promise code that you can then subscribe to the desired event and then resolve the promise in that subscribed event handler. – jfriend00 Jun 02 '19 at 03:55
  • The beauty of using events for this is that the code isn't hard coupled. The code receiving the event doesn't know anything about the promise. It just makes sure an event is available to any outside code that wants to know about it. Modules retain their independence and can hook up to each other as desired. – jfriend00 Jun 02 '19 at 03:56
  • See my answer to this other question for how to handle asynchronous code in class constructors: https://stackoverflow.com/questions/43431550/async-await-class-constructor/43433773#43433773 – slebetman Jun 02 '19 at 04:09
  • @jfriend00 would you be able to clarify what you mean by " create an event that the promise can subscribe to" ? – pleasedontcallmethat Jun 02 '19 at 18:56
  • Apples and pears. Promises are promises. Polling is polling. Neither is a substitute for the other. – Roamer-1888 Jun 02 '19 at 19:31
  • I'm going to pass on further suggestions until you show your actual code that gets the data that would initiate the event because it's way inefficient to try to offer a generic tutorial rather than show you how to fix your actual code. But, if you want to go research it yourself, go look at [EventEmitter](https://nodejs.org/api/events.html). – jfriend00 Jun 02 '19 at 23:16

4 Answers4

1

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 ");
jib
  • 40,579
  • 17
  • 100
  • 158
  • No polling required if the OP is just waiting for a message to be received from some other entity (which I assume means some incoming TCP something). – jfriend00 Jun 02 '19 at 01:46
  • Sure, there are usually better ways. But I'm answering the literal question about "an alternative to polling", which promises alone are not. I interpret the OP to not be in control of the `stateReady` function. I thought that was the interesting bit here. – jib Jun 02 '19 at 02:52
  • Read their comment under the question. Questio. Is incomplete by itself. – jfriend00 Jun 02 '19 at 02:58
  • I think it's a good question as phrased. I can see people wondering if promises can be resolved by state changes. – jib Jun 02 '19 at 03:06
  • Without additional context, it's a nebulous question that doesn't really describe the actual OP's situation is (see comments for more detail) and as such is incomplete. Telling the OP you still need to poll somewhere is simply not correct. Whatever code flips the state change can also resolve the promise - that's the desired way to code problems like this. So, the devil is in the detail of how the state gets changed and incomplete without that additional context. For anyone with this problem, the issue is how the state gets changed and what you can do in that code. – jfriend00 Jun 02 '19 at 03:22
  • 1
    Definitely my fault for not posting enough context, I suppose my example posted wasn't the best representation of my issue, but thank you @jib for explaining that the promise executor only goes off once, and never again. I assumed it behaved in a polling manner. – pleasedontcallmethat Jun 02 '19 at 03:58
0

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.
Evert
  • 93,428
  • 18
  • 118
  • 189
0

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!');
}
Randy Casburn
  • 13,840
  • 1
  • 16
  • 31
0

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

slebetman
  • 109,858
  • 19
  • 140
  • 171
  • Thanks for your response. So, to get this right, you're suggesting to add a method to the constructor of my connection object that handles the creation of a connection, the sending of a message, and the response from the server, all within the promise? I.e. they all need to run in the same scope under that promise? – pleasedontcallmethat Jun 02 '19 at 19:39
  • It's not about scope. It's about timing. If you look at the example I provide, I establish the connection **outside** the scope of the constructor in a separate method – slebetman Jun 02 '19 at 21:51
  • @pleasedontcallmethat Are you using ES6 classes or traditional constructor/prototype? – slebetman Jun 02 '19 at 21:52
  • I'm using ES6 Classes – pleasedontcallmethat Jun 02 '19 at 22:20
  • Sorry, I think I'm getting my terminology and semantics wrong. I see now the db() method of MyClass is outside the constructor. – pleasedontcallmethat Jun 02 '19 at 22:21