Asynchronously delay JS until a condition is met
Asked Answered
S

4

10

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).

Substrate answered 26/9, 2011 at 18:16 Comment(3)
Why don't you use the callback from the HTTP request?Sitarski
I can't use that callback because render might be called before the JSON is returned, or 10 minutes after it is returned. Basically, I want to be able to call render() at any point in time I want.Substrate
You can still use the callback. In render, check whether the JSON has been returned, and, if it hasn't, add to an array of callbacks. Or just use jQuery's new Deferred objects, which does this for you.Sitarski
J
31

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/

Junko answered 26/9, 2011 at 18:22 Comment(0)
D
1

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)();
    }
})();
Donata answered 15/4, 2019 at 15:18 Comment(0)
B
0

we can use await new Promise((resolve) and call resolve when our condition is true : we can run whole code in browser with audioPermission On.

(async () => {
const getVolume = async () => {
let n = 0;
let abc = async () => {
  let count = 0;
  try {
    let audioStream = await navigator.mediaDevices.getUserMedia({
      audio: {
        echoCancellation: true,
      },
    });
    let audioContext = new AudioContext();

    let audioSource = audioContext.createMediaStreamSource(audioStream);
    let analyser = audioContext.createAnalyser();
    analyser.fftSize = 512;
    analyser.minDecibels = -127;
    analyser.maxDecibels = 0;
    analyser.smoothingTimeConstant = 0.4;
    audioSource.connect(analyser);
    let volumes = new Uint8Array(analyser.frequencyBinCount);
    console.log("volume__________________");
    console.log(volumes);

    await new Promise((resolve) => {
      const timer = setInterval(() => {
        count += 1;
        console.log("count = " + count);

        analyser.getByteFrequencyData(volumes);
        console.log("volume__________________");
        console.log(volumes);

        if (count == 3 || volumes[0] > 0) {
          clearInterval(timer);
          n = volumes[0];
          console.log("count__________________" + count);
          resolve(n);
        }
      }, 2000);
    });
  } catch (e) {
    console.error(
      "Failed to initialize volume visualizer, simulating instead...",
      e
    );
    return 0;
  }

  console.log("n inside______________ = " + n);
  return n;
};

await abc();

return n;
};

let t = await getVolume();
console.log(t);
})();
Bureaucracy answered 27/5, 2022 at 9:32 Comment(0)
T
-1

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.

Thylacine answered 8/2, 2016 at 20:5 Comment(1)
500 milliseconds is a lifetime yes, but holding the JS thread hostage you are not. Async functions in javascript are not thread-blocking, they are queued for execution at some point in the future. The accepted answer is common because it is idiomatic javascript, and is as performant as you can go when you don't have a "ready" event you can respond to.Dinesh

© 2022 - 2024 — McMap. All rights reserved.