Promises and setTimeout

2019-07-10 13:08发布

问题:

I am trying to determine a way to "pause" my Promises code until a condition is true, perhaps by using a recursive setTimeout(). For my simple example, I am manually setting waitValue.

I need to wait for a moment, and if waitValue is still false, then merely continue to wait. And of course when waitValue becomes true, then continue processing. Here is what i have pieced together so far:

var counter=0;
function promiseTest() { 
    return new Promise(  (resolve, reject) => {
        if  ( waitValue )  {
            console.log('waitValue is now true');
            resolve('FIRST PROMISE');
        } else  {                      // we need to wait again
            if  ( counter > 1000 ) {   // dont wait forever.
                reject('waited too long');
            } else {
                console.log('WAIT MESSAGE: ' + counter++ );
                setTimeout( promiseTest, 3000);
            }
        }
    })
    .then(   result => {
        return(`SECOND PROMISE:  the result is: ${result}`);
    });
}

And to use it:

promiseTest().then( (result)=>{ console.log('LOCAL MESSAGE: ' + result); });

The following works fine:

var waitValue = true;
promiseTest().then( (result)=>{ console.log('LOCAL MESSAGE: ' + result); });
// waitValue is now true
// LOCAL MESSAGE: SECOND PROMISE:  the result is: FIRST PROMISE

However, the following does not seem to complete as i wanted:

var waitValue = false;
promiseTest().then( (result)=>{ console.log('LOCAL MESSAGE: ' + result); });
// waiting messages appear as expected
waitValue = true;
// waitValue is now true
// no other messages

I have been unable to find a promises example to temporarily execution. An ordinary javaScript example might look like this:

var waitValue = false;
var counter = 0;
(function tester() {
   if  ( waitValue ) {
      console.log('finally true');
   } else {
       if  ( counter > 1000 ) {
           console.log('waited too long');
           process.exit;
       } else {
           console.log('still waiting, counter = ' + counter++);
           setTimeout(tester, 1000);
       }
   }
})();
// wait a few seconds and enter this into the console:
var waitValue = false;

What would a promises script look like to temporarily pause execution? Or maybe Promises should not be used like this at all?

Thank you very much.

回答1:

The idea is in the right direction. You just need to resolve the current promise also when you have called the function recursively, otherwise your current promise will never fulfil.

Note however, that you create a stack of promises if the wait is long.

function promiseTest(counter = 1000) { 
    return new Promise(  (resolve, reject) => {
        if ( waitValue )  {
            console.log('waitValue is now true');
            resolve('FIRST PROMISE');
        } else if  ( counter <= 0 ) {   // dont wait forever.
            reject('waited too long');
        } else {
            console.log('Count down: ' + counter);
            setTimeout( _ => { // make sure to call `resolve` after the nested promise resolved:
                promiseTest(counter-1).then(resolve);
            }, 3000);
        }
    })
    .then(   result => {
        return `SECOND PROMISE:  the result is: ${result}`;
    });
}

var waitValue = false;

promiseTest().then ( result => {
   console.log('done:', result);
});

// schedule the change of the waitValue:
setTimeout(_ => waitValue = true, 4000);

Note how the output will have some traces of each of the nested chained promises that resolved.

Alternative

I find it more intuitive to perform the recursive call on a function you define within the Promise constructor callback, not on the function that creates the Promise. That way you only create one promise, and you avoid the promise constructor anti-pattern which is present in your idea (and the above working version of it):

function promiseTest(counter = 1000) { 
    return new Promise(  (resolve, reject) => {
        (function loop(counter) {
            if ( waitValue )  {
                console.log('waitValue is now true');
                resolve('FIRST PROMISE');
            } else if  ( counter <= 0 ) {   // dont wait forever.
                reject('waited too long');
            } else {
                console.log('Count down: ' + counter);
                setTimeout( loop.bind(null, counter-1), 3000);
            }
        })(counter); // initial value of count-down
    })
    .then(   result => {
        return `SECOND PROMISE:  the result is: ${result}`;
    });
}

var waitValue = false;

promiseTest().then ( result => {
   console.log('done:', result);
});

// schedule the change of the waitValue:
setTimeout(_ => waitValue = true, 4000);

Note how the output is slightly different from the first version, which reflects that there is only one new promise involved

NB: it is not essential, but I prefer counting down from some value (1000), and to pass it as an argument to the anonymous function that does the looping.



回答2:

setTimeout( promiseTest, 3000); will not work, this might call the promiseTest function again but never resolve the promise created in the outermost invocation.

Instead of messing around with callbacks that much, promisify the asynchronous primitive that you are using, setTimeout:

function wait(t) {
    return new Promise(resolve => {
        setTimeout(resolve, t);
    });
}

and then use that in your polling function:

function promiseTest(counter = 1000) {
    if (waitValue) {
        console.log('waitValue is now true');
        return Promise.resolve('FIRST PROMISE');
    } else if (counter <= 0) { // dont wait forever.
        return Promise.reject(new Error('waited too long'));
    } else {
        console.log('WAIT MESSAGE: ' + counter );
        return wait(3000).then(() => {
            return promiseTest(counter-1);
        });
    }
}