6

I need to sleep the code until some condition is met or a 3 second timeout is passed. then return a simple string. Is there anyway I can do this?

// this function needs to return a simple string 

function something() { 

    var conditionOk = false;

    var jobWillBeDoneInNMiliseconds = Math.floor(Math.random() * 10000);

    setTimeout(function() {

        // I need to do something here, but I don't know how long it takes
        conditionOk = true; 

    }, jobWillBeDoneInNMiliseconds);


    // I need to stop right here until
    // stop here until ( 3000 timeout is passed ) or ( conditionOk == true )
    StopHereUntil( conditionOk, 3000 );

    return "returned something"; 
}

here is what I exactly going to do:

I make the browser scroll to bottom of the page, then some ajax function will be called to fetch the comments (that I have not control on it). Now I need to wait until comments are appeared in document with ".comment" class.

I need the getComments() function return comments as a json string.

function getComments() {

    window.scrollTo(0, document.body.scrollHeight || document.documentElement.scrollHeight);

  var a = (document.querySelectorAll('div.comment'))

  // wait here until  (  a.length > 0  ) or ( 3 second is passed )

  // then I need to collect comments
  var comments = [];
  document.querySelectorAll('div.comment p')
    .forEach(function(el){      
        comments.push(el.text());
    });

  return JSON.stringify(comments);
} 

getComments();
mdaliyan
  • 93
  • 1
  • 1
  • 6
  • this is not enough information: What condition will result in `OK` or `false`? This is not a too trivial task, usually such things are solved using `Observable`s / `EventEmitter`s – messerbill Mar 01 '18 at 16:54
  • I created a simple example in the code. for example. `condition` variable will be true in about 1 to 9 seconds. I need the code wait just for for 3 seconds until that variable changed to `true` – mdaliyan Mar 01 '18 at 16:57
  • due to `JavaScript` is executed `asynchronously` i need to see the code snippet evaluating if it succeeded or not. Maybe an `ajax` request? this can not be answered without more information. – messerbill Mar 01 '18 at 16:58
  • 1
    you can't really return something from a function which is going to hand off processing to an asynchronous function. sounds like you might want to use something like a promise https://developers.google.com/web/fundamentals/primers/promises – Toby Mar 01 '18 at 17:02
  • Please review and let me know whether it's your issue or not? https://jsfiddle.net/qxw54mzs/16/ – Dipak Mar 01 '18 at 17:03
  • @Toby `Observable` or a `Promise` or just a simple `EventEmitter` - OT needs to update the question... – messerbill Mar 01 '18 at 17:04
  • I don't have the complete code yet. you guessed right. I need to wait till an ajax call is completed that i have not sent. I will post a concept in a second. – mdaliyan Mar 01 '18 at 17:05
  • If you are using an ajax call, why can't you just handle the ajax response when it is received using an event listener? – Toby Mar 01 '18 at 17:06
  • I have edited my question. this is the second example is what i want to do. – mdaliyan Mar 01 '18 at 17:17
  • no. I saw `https://jsfiddle.net/qxw54mzs/16/`... if you change the last line with `console.log(something())' you see the the `someshing()` function returns `returned something` immediately and does not wait. – mdaliyan Mar 01 '18 at 17:49
  • I have no control on the ajax call. so I cannot rise any event. – mdaliyan Mar 01 '18 at 17:50

7 Answers7

8

I came across this problem and none of the solutions were satisfactory. I needed to wait until a certain element appeared in the DOM. So I took hedgehog125's answer and improved it to my needs. I think this answers the original question.

const sleepUntil = async (f, timeoutMs) => {
    return new Promise((resolve, reject) => {
        const timeWas = new Date();
        const wait = setInterval(function() {
            if (f()) {
                console.log("resolved after", new Date() - timeWas, "ms");
                clearInterval(wait);
                resolve();
            } else if (new Date() - timeWas > timeoutMs) { // Timeout
                console.log("rejected after", new Date() - timeWas, "ms");
                clearInterval(wait);
                reject();
            }
        }, 20);
    });
}

Usage (async/await promise):

try {
    await sleepUntil(() => document.querySelector('.my-selector'), 5000);
    // ready
} catch {
    // timeout
}

Usage (.then promise):

sleepUntil(() => document.querySelector('.my-selector'), 5000)
    .then(() => {
        // ready
    }).catch(() => {
        // timeout
    });
pbotas
  • 349
  • 3
  • 13
  • 1
    the variables timeWas as well as wait is not declared in your example – CodingYourLife Mar 24 '21 at 14:46
  • @CodingYourLife thinking back about it, I don't know why I didn't declare the variables properly within the function scope. I will edit the answer now. Thanks for noticing it. – pbotas Mar 24 '21 at 15:28
  • `Uncaught SyntaxError: await is only valid in async functions and the top level bodies of modules` – Kirk Hammett Mar 11 '22 at 11:00
  • @KirkHammett you have a good explanation for that [here](https://stackoverflow.com/a/49432604/7649295). In that case you should use `then`. I'm gonna improve the answer for that. – pbotas Mar 12 '22 at 18:30
3

You should be able to achieve this using Promise.race. Here's a basic example:

let promise1 = new Promise(resolve => {
  setTimeout(resolve, 500, 'one');
});
let promise2 = new Promise(resolve => {
  setTimeout(resolve, 800, 'two');
});

async function fetchAndLogResult() {
  let result = await Promise.race([promise1, promise2]);
  console.log(result);
}

fetchAndLogResult();

Here's an alternative version, more concise although not using async/await:

let promise1 = new Promise(resolve => {
  setTimeout(resolve, 500, 'one');
});
let promise2 = new Promise(resolve => {
  setTimeout(resolve, 800, 'two');
});

Promise.race([promise1, promise2]).then(result => console.log(result));
Jeto
  • 14,596
  • 2
  • 32
  • 46
  • On any relatively modern browser yes. Otherwise you have tools like [Babel](http://babeljs.io/) to transpile to a cross-browser equivalent. – Jeto Mar 01 '18 at 17:16
  • sorry. that `Promise.race` returns a `Promise { }` object. I need to get a string. – mdaliyan Mar 01 '18 at 17:31
  • @mdaliyan That's why you use `await`. Check the snippet: `result` is a string and that what is displayed to the console. The only requirement is that the function you're doing this in is marked `async`. I'll edit in a different version of doing the same thing though, maybe it'll help. – Jeto Mar 01 '18 at 18:10
  • @mdaliyan There you go, post edited. So what you basically need to do is replace the promises with your actual tasks (call `resolve` with the result when the task is done), e.g. `new Promise(resolve => resolve(performTaskAndReturnResult()))` – Jeto Mar 01 '18 at 18:14
  • I cannot return `Promise`. I can't even use `Promise.resolve()`. The function needs to return simple `string`. – mdaliyan Mar 01 '18 at 18:30
  • Say your function is called like this: `let result = myFunction();`: you'll just need to change it to `let result = await myFunction();` (and mark your current function as `async`) and `result` will be a string as you want it to be. Or alternatively, `myFunction().then(result => { // handle result here });` – Jeto Mar 01 '18 at 18:34
  • I can't use `async` and `await`. It's an evaluation function running on chromedebugtools that i have no control on it. I can only write contents of the function – mdaliyan Mar 01 '18 at 19:35
1

In JavaScript there isn't a way to wait. You can either use settimeout or you can use a while loop (bare in mind that scripts can't run while this is happening and then the page could become unresponsive).

With settimeout

// this function needs to return a simple string 

function something() { 

    conditionOk = false;

    var jobWillBeDoneInNMiliseconds = Math.floor(Math.random() * 10000);

    timeout = setTimeout(function() {

        // I need to do something here, but I don't know how long it takes
        conditionOk = true; 

    }, jobWillBeDoneInNMiliseconds);


    // I need to stop right here until
    // stop here until ( 3000 timeout is passed ) or ( conditionOk ==     true )

    timeWas = new Date();

    wait = setInterval(function() {
        if (conditionOk) {
            // Communicate what you were trying to return using globals
            clearInterval(wait);
        }
        if (new Date() - timeWas > 3000) { // Timeout
            // Clear this interval
            clearInterval(wait);
        }
    }, 30);
}

With while

// this function needs to return a simple string 

function something() { 

    conditionOk = false;

    var jobWillBeDoneInNMiliseconds = Math.floor(Math.random() * 10000);

    timeout = setTimeout(function() {

        // I need to do something here, but I don't know how long it takes
        conditionOk = true; 

    }, jobWillBeDoneInNMiliseconds);


    // I need to stop right here until
    // stop here until ( 3000 timeout is passed ) or ( conditionOk ==     true )

    timeWas = new Date();

    while ((! conditionOk) && (! (new Date() - timeWas > 3000))) { // 3000 = the delay
        // Do nothing
    }
    if (conditionOk) {
        return "returned something";
    }
    else {
        return "returned nothing";
    }
}

You might also want to look at this question: JavaScript sleep/wait before continuing

Hope this helps!

Nico
  • 83
  • 1
  • 7
1

I had this question opened in my browser while I was looking for a similar solution. Probably post author doesn't need it anymore, but here goes to others coming from non event-looped worlds (php, java, python).

Here's what I got after reading MDN: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Operators/await https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Statements/async_function

These two describe what is needed. You'll need some helper functions for that. It looks bloated, but it seems there's no other way around in JS. Gets the job done though :)

// sleep helper
function sleep(ms) {
    return new Promise(resolve => setTimeout(resolve, ms))
}

// func that will check your condition
function checkCondition(number) {
    if (number == 5) return true;
    else return false;
}

// helper that will wait and then check the condition
// await halts the execution flow until sleep is resolved
// this is the part that you need
async function waitOneSecondAndCheckCondition(number) {
    const v = await sleep(500);
    return checkCondition(number);
}

// since most likely you want to poll the checkCondition
// and not waste all the time waiting
// this polls conditon every half a second
// and in the end return a simple string
// either when condition is met or after three seconds
async function waitUntilConditionIsMetOrTimeoutIsPassed() {
    for (let i = 0; i < 6; i++) {
        let result = await waitOneSecondAndCheckCondition(i);
        console.log("i is: " + i + " condition result is: " + result);
        if (!result) continue;
        else break;
    }
}

waitUntilConditionIsMetOrTimeoutIsPassed();

console output if condition is met at some point within 3s:

i is: 0 condition result is: false
i is: 1 condition result is: false
i is: 2 condition result is: false
i is: 3 condition result is: true
a simple string

Console output when a timeout occurred:

i is: 0 condition result is: false
i is: 1 condition result is: false
i is: 2 condition result is: false
i is: 3 condition result is: false
i is: 4 condition result is: false
i is: 5 condition result is: false
a simple string

Hope this helps all of you JS newcomers as me :)

sserzant
  • 66
  • 2
1

If you want to wait until a condition is met:

main();

async function main() {
  let foo = 0;
  // for demo purposes, artificially increment foo
  setInterval(() => foo++);
  console.log('Waiting until foo reaches 1337 ...');
  await until(() => foo === 1337);
  console.log('foo === ' + foo);
}

function until(condition) {
  return new Promise((resolve) => {
    const interval = setInterval(() => {
      if (condition()) {
        clearInterval(interval);
        resolve();
      }
    });
  });
}

If you want to wait until a certain amount of time has passed:

main();

async function main() {
  console.log('Waiting 2 seconds ...');
  await milliseconds(2_000);
  console.log('Done!');
}

function milliseconds(ms) {
  return new Promise((resolve) => setTimeout(resolve, ms));
}
GirkovArpa
  • 4,427
  • 4
  • 14
  • 43
0

Ok because you are using ajax you can do the following:

var eventEmitter = new EventEmitter()
eventEmitter.on('myEvent', myFunction)

$.ajax(...).then(function() {
  eventEmitter.emit('myEvent', {state: true})
})

setTimeout(function() { eventEmitter.emit('myEvent', {state: false}), 3000);

function myFunction() {
   //you can do your checks inside here
}

your ajax without using jQuery:

var xhr = new XMLHttpRequest();
xhr.open('GET', 'myservice/username?id=some-unique-id');
xhr.onload = function() {
  if (xhr.status === 200) {
    eventEmitter.emit('myEvent', {state: true})
  }
  else {
    alert('Request failed.  Returned status of ' + xhr.status);
  }
};
xhr.send();
messerbill
  • 5,499
  • 1
  • 27
  • 38
  • I'm not the one who sends the ajax request. I can only scroll to bottom of the page and wait until the comments are fetched. I have edited my question. see the second code. – mdaliyan Mar 01 '18 at 17:27
0

For Typescript, I've modified @pbotas answer like this:

export const sleepUntil = async (f: () => boolean, timeoutMs: number) => {
  return new Promise((resolve, reject) => {
    const timeWas = new Date();
    const wait = setInterval(function () {
      if (f()) {
        console.log('resolved after', +new Date() - +timeWas, 'ms');
        clearInterval(wait);
        resolve(true);
      } else if (+new Date() - +timeWas > timeoutMs) {
        // Timeout
        console.log('rejected after', +new Date() - +timeWas, 'ms');
        clearInterval(wait);
        reject(false);
      }
    }, 20);
  });
};

Notice that I'm evaluation a boolean condition.

For invoking this function:

sleepUntil(() => someBoolean, 5000)
 .then(() => {
    // ready
 })
 .catch(() => {
   // timeout
 });
Osama Bin Saleem
  • 779
  • 1
  • 12
  • 24