Asynchronously delay JS until a condition is met

2020-05-27 10:57发布

I have a class, ChatRoom, that can only render after it receives a long-running HTTP request (it could take 1 second or 30 seconds). So I need to delay rendering until ChatRoom.json is not null.

In the code below, I'm using Closure Library's goog.async.ConditionalDelay. It works, but is there a better way (maybe without needing Closure Library) to do this?

ChatRoom.prototype.json = null; // received after a long-running HTTP request.

ChatRoom.prototype.render = function() {
    var thisChatRoom = this;

    function onReady() {
        console.log("Received JSON", thisChatRoom.json);
        // Do rendering...
    }

    function onFailure() {
        alert('Sorry, an error occurred. The chat room couldn\'t open');
    }

    function isReady() {
        if (thisChatRoom.json != null) {
            return true;
        }
        console.log("Waiting for chat room JSON...");
        return false;
    }

    // If there is a JSON request in progress, wait until it completes.
    if (isReady()) {
        onReady();
    } else {
        var delay = new goog.async.ConditionalDelay(isReady);
        delay.onSuccess = onReady;
        delay.onFailure = onFailure;
        delay.start(500, 5000);
    }
}

Note that "while (json == null) { }" isn't possible because that would be synchronous (blocking all other JS execution).

3条回答
Bombasti
2楼-- · 2020-05-27 11:12

The answer I came up with is like this:

var count = 0;
// Number of functions that need to run. This can be dynamically generated
// In this case I call check(data, cb) a total of 3 times
var functionNum = 3; 
function toCallAfter(){
    console.log('I am a delayed function');
}

I had this for a check function that ran once regularly and twice in a loop:

check(data, function(err){ // check is my asynchronous function to check data integrity
    if (err){
        return cb(null, { // cb() is the return function for the containing function
            errCode: 'MISSINGINFO',
            statusCode: 403,
            message : 'All mandatory fields must be filled in'
        });
    } // This part here is an implicit else
    count++; // Increment count each time required functions complete to
             // keep track of how many function have completed
    if (count === functionNum) {
        return anon();
    }
    return;
});
// Run twice more in a loop
for(var i = 0; i < 2; i++) {
    check(data, function(err) { // calls check again in a loop
        if (err){
            return cb(null, {
                errCode: 'MISSINGINFO',
                statusCode: 403,
                message : 'All mandatory fields must be filled in'
            });
        }
        count++;
        if (count === functionNum) {
            return toCallAfter();
        }
        return;
    });
}

Lastly, I'd like to point out a significant performance fault in the alternate (and extraordinarily common) answer:

(function wait() {
    if ( chatroom.json ) {
        chatroom.render();
    } else {
        setTimeout( wait, 500 );
    }
})();

In this case, you are essentially holding the browser or server (If using node.js) hostage for 500 milliseconds for every check which is an incredibly long time for a computer. Meaning a huge performance hit. My solution of directly keeping track of the required completed functions is free from time restraints and will run instantly as soon as all functions complete.

查看更多
Juvenile、少年°
3楼-- · 2020-05-27 11:25

You could also achieve this using lodash's debouncer with recursion.

import _debounce from 'lodash/debounce';

const wait = (() => {
    if ( chatroom.json ) {
        chatroom.render();
    } else {
      _debounce(wait, 500)();
    }
})();

查看更多
淡お忘
4楼-- · 2020-05-27 11:32

Consider this:

(function wait() {
    if ( chatroom.json ) {
        chatroom.render();
    } else {
        setTimeout( wait, 500 );
    }
})();

This will check every half second.

Live demo: http://jsfiddle.net/kBgTx/

查看更多
登录 后发表回答