Having trouble promisifying setInterval. The .then() is occurring immediately despite the embedEditor function returning a promise only if time === 0. How do I make a function in setInterval return a promise once a time period has completed?
const interval = setInterval(embedEditor, 1000);
async function embedEditor() {
time -= 1;
//inner function....//
if (time === 0) {
clearInterval(interval);
return new Promise((res) => { res("time period over"); });
}
}
await interaction.reply({ embeds: [exampleEmbed], components: [row, row2] });
await interval
.then(/*action*/);
Answer from Fannie Zemlak that works like a charm:
const asyncInterval = async (callback, ms, triesLeft = 5) => {
return new Promise((resolve, reject) => {
const interval = setInterval(async () => {
if (await callback()) {
resolve();
clearInterval(interval);
} else if (triesLeft <= 1) {
reject();
clearInterval(interval);
}
triesLeft--;
}, ms);
});
}
How to stop or reset the execution of a function?
I tried to add a value with the start number to the "while(true)" and return if the number of the launched eval does not match "if (RUN != 39629) return;", but it seems to me that this does not always work correctly. I would like to somehow reliably stop the execution of a running eval.
let a = 0;
let code = `while(true){
await sleep(1000);
cons('step:'+a);
a++;
}`;
$('span').click(e => {
if ($(e.target).text() == 'start') {
cons('start');
eval("(async () => {" + code + "})()");
}
});
function sleep(ms) {
return new Promise((resolve) => {
setTimeout(resolve, ms);
});
}
function cons(s)
{
$('div').html($('div').html()+ s +'<br>');
}
span { cursor: pointer; }
<script src="https://cdnjs.cloudflare.com/ajax/libs/jquery/3.3.1/jquery.min.js"></script>
<span>start</span> <span>stop</span>
<div>...<br></div>
The following works to stop the execution of the loop and restart it. It's using a separate done variable to check in the loop.
However, note that this only works because sleep is using setTimeout during which the handlers from jQuery can be triggered. Without that it wouldn't work as JS is single-threaded, see this question. The stop handler would not be called and the loop run forever.
let done = false;
let a = 0;
let code = `while(!done){
await sleep(1000);
cons('step:'+a);
a++;
}`;
$('span').click(e => {
const target = $(e.target).text()
if (target === 'start') {
done = false
a = 0
cons('start');
eval("(async () => {" + code + "})()");
}
if (target === 'stop') {
done = true
}
});
function sleep(ms) {
return new Promise((resolve) => {
setTimeout(resolve, ms);
});
}
function cons(s)
{
$('div').html($('div').html()+ s +'<br>');
}
span { cursor: pointer; }
<script src="https://cdnjs.cloudflare.com/ajax/libs/jquery/3.3.1/jquery.min.js"></script>
<span>start</span> <span>stop</span>
<div>...<br></div>
Added at the request to show an example with a return variable and what is wrong with it. In order for it to work correctly, it must be inserted into each line of code in order to be able to interrupt the execution of eval at really any time, otherwise part of the code is still executed after clicking on the stop.
start stop
...
start
step:0
next:0
step:1
next:1
step:2
stop <-!!!!!
next:2 <-!!!!!
let a = 0;
let run = 0;
let code = `while(true){
if (run != 100) return;
cons('step:'+a);
await sleep(5000);
cons('next:'+a);
a++;
}`;
$('span').click(e => {
if ($(e.target).text() == 'start') {
run = 100;
cons('start');
eval("(async () => {" + code + "})()");
}
if ($(e.target).text() == 'stop') {
run = 0;
cons('stop');
}
});
function sleep(ms) {
return new Promise((resolve) => {
setTimeout(resolve, ms);
});
}
function cons(s)
{
$('div').html($('div').html()+ s +'<br>');
}
span { cursor: pointer; }
<script src="https://cdnjs.cloudflare.com/ajax/libs/jquery/3.3.1/jquery.min.js"></script>
<span>start</span> <span>stop</span>
<div>...<br></div>
I have such code: (I put here the whole code in order not to miss anything)
let updateCoverLoaded;
function coverLoaded(cover){
clearInterval(updateCoverLoaded);
updateCoverLoaded = setInterval(() => coverLoaded(cover), 25);
const coverSelector = $(`[aria-label="${cover}"]`).find("image")[0];
return new Promise(resolve => {
if(typeof coverSelector !== "undefined"){
const coverSelectorSource = coverSelector.getAttribute("xlink:href");
if(getCoverWidth(coverSelectorSource) !== 0) {
clearInterval(updateCoverLoaded);
setTimeout(() => {
player.SetVar(`${cover}`, true);
setTimeout(() => player.SetVar(`${cover}`, false), 100);
resolve();
}, 100);
}
}
});
function getCoverWidth(src) {
const image = new Image();
image.src = src;
return image.naturalWidth;
}
}
// Add SFX to cover entrance and exit
async function feedbackCover(){
await coverLoaded('coverB');
console.log('after resoving promise as expected!');
}
Although code reaches the player.SetVar(${cover}, true); line of code and I'm sure that resolve() is executed the promise doesn't resolve and I can't see console.log('after resoving promise as expected!'); Why? it works in some other situations!
Note: The code actually waits for an element (an image) to load and then resolves the promise.
There are two possible problems, the first is that if the image isn't loaded yet it the first Promise doesn't resolve and all subsequent Promises are discarded, the second is that if coverLoaded is called again, the first promise will never resolve.
This should work as expected:
function coverLoaded(cover) {
return new Promise((resolve) => {
const updateCoverLoaded = setInterval(() => {
const coverSelector = $(`[aria-label="${cover}"]`).find("image")[0];
if (typeof coverSelector !== "undefined") {
const coverSelectorSource = coverSelector.getAttribute("xlink:href");
if (getCoverWidth(coverSelectorSource) !== 0) {
clearInterval(updateCoverLoaded);
setTimeout(() => {
player.SetVar(`${cover}`, true);
setTimeout(() => player.SetVar(`${cover}`, false), 100);
resolve();
}, 100);
}
}
}, 25);
});
function getCoverWidth(src) {
const image = new Image();
image.src = src;
return image.naturalWidth;
}
}
// Add SFX to cover entrance and exit
async function feedbackCover() {
await coverLoaded("coverB");
console.log("after resoving promise as expected!");
}
I am trying to make a timer, and trying to exit from the callback hell that is happening inside the code I am writing, this is a part from the code I have written, the problem is that I am not receiving the feedback from the promise, so no resolve message, no reject message.
what is the problem?
var timer = 15;
var startTimer = (timer) => {
return new Promise((resolve, reject)=>{
if(resolve){
var countDown = setInterval(() => {
console.log(timer);
timer--;
if (timer == 0) {
clearInterval(countDown);
return "YOU ARE LOSER";
}
}, 1000);
}
if(reject){
return "sorry something went wrong!";
}
})
}
startTimer(timer)
.then(message =>{
console.log(message);
//the message should be "You are loser!".
})
.catch(message =>{
console.log(message);
})
resolve and reject are functions to call. resolve is for when the asynchronous operation has completed, and reject for when an error has occurred. When invoking these functions, you can supply a value to propagate along the promise chain.
var count = 5
var startTimer = (count)=>{
return new Promise((resolve,reject)=>{
try {
var intervalId = setInterval(()=>{
if(count) {
console.log(count--)
return
}
clearInterval(intervalId)
resolve( "YOU ARE LOSER")
}, 1000)
} catch {
reject('sorry something went wrong!')
}
})
}
startTimer(count)
.then((result)=>{ console.log(result) })
.catch((err)=>{ console.log(err) })
You're using promises wrong, take a look at this friendly tutorial.
You resolve or reject the value that you want to return.
var timer = 15;
var startTimer = (timer) => {
return new Promise((resolve, reject) => {
try {
var countDown = setInterval(() => {
console.log(timer);
timer--;
if (timer == 0) {
clearInterval(countDown);
resolve("YOU ARE LOSER");
}
}, 1000);
} catch (err) {
reject("sorry something went wrong!");
}
})
}
startTimer(timer)
.then(message => {
console.log(message);
})
.catch(message => {
console.log(message);
})
Simplified version without promises
var timer = 10;
var countDown = 0;
var startTimer = fn => setInterval(() => {
console.log(timer);
if (--timer === 0)
{
clearInterval(countDown);
console.log("You lose");
if(fn)
fn();
}
}, 500);
function onTimerFinished() {
console.log("Timer has finished");
}
try {
countDown = startTimer(onTimerFinished);
} catch (err) {
console.error(err);
}
I'm using navigator.geolocation.watchPosition in JavaScript, and I want a way to deal with the possibility that the user might submit a form relying on location before watchPosition has found its location.
Ideally the user would see a 'Waiting for location' message periodically until the location was obtained, then the form would submit.
However, I'm not sure how to implement this in JavaScript given its lack of a wait function.
Current code:
var current_latlng = null;
function gpsSuccess(pos){
//console.log('gpsSuccess');
if (pos.coords) {
lat = pos.coords.latitude;
lng = pos.coords.longitude;
}
else {
lat = pos.latitude;
lng = pos.longitude;
}
current_latlng = new google.maps.LatLng(lat, lng);
}
watchId = navigator.geolocation.watchPosition(gpsSuccess,
gpsFail, {timeout:5000, maximumAge: 300000});
$('#route-form').submit(function(event) {
// User submits form, we need their location...
while(current_location==null) {
toastMessage('Waiting for your location...');
wait(500); // What should I use instead?
}
// Continue with location found...
});
Modern solution using Promise
function waitFor(conditionFunction) {
const poll = resolve => {
if(conditionFunction()) resolve();
else setTimeout(_ => poll(resolve), 400);
}
return new Promise(poll);
}
Usage
waitFor(_ => flag === true)
.then(_ => console.log('the wait is over!'));
or
async function demo() {
await waitFor(_ => flag === true);
console.log('the wait is over!');
}
References
Promises
Arrow Functions
Async/Await
Personally, I use a waitfor() function which encapsulates a setTimeout():
//**********************************************************************
// function waitfor - Wait until a condition is met
//
// Needed parameters:
// test: function that returns a value
// expectedValue: the value of the test function we are waiting for
// msec: delay between the calls to test
// callback: function to execute when the condition is met
// Parameters for debugging:
// count: used to count the loops
// source: a string to specify an ID, a message, etc
//**********************************************************************
function waitfor(test, expectedValue, msec, count, source, callback) {
// Check if condition met. If not, re-check later (msec).
while (test() !== expectedValue) {
count++;
setTimeout(function() {
waitfor(test, expectedValue, msec, count, source, callback);
}, msec);
return;
}
// Condition finally met. callback() can be executed.
console.log(source + ': ' + test() + ', expected: ' + expectedValue + ', ' + count + ' loops.');
callback();
}
I use my waitfor() function in the following way:
var _TIMEOUT = 50; // waitfor test rate [msec]
var bBusy = true; // Busy flag (will be changed somewhere else in the code)
...
// Test a flag
function _isBusy() {
return bBusy;
}
...
// Wait until idle (busy must be false)
waitfor(_isBusy, false, _TIMEOUT, 0, 'play->busy false', function() {
alert('The show can resume !');
});
This is precisely what promises were invented and implemented (since OP asked his question) for.
See all of the various implementations, eg promisejs.org
You'll want to use setTimeout:
function checkAndSubmit(form) {
var location = getLocation();
if (!location) {
setTimeout(checkAndSubmit, 500, form); // setTimeout(func, timeMS, params...)
} else {
// Set location on form here if it isn't in getLocation()
form.submit();
}
}
... where getLocation looks up your location.
You could use a timeout to try to re-submit the form:
$('#route-form').submit(function(event) {
// User submits form, we need their location...
if(current_location==null) {
toastMessage('Waiting for your location...');
setTimeout(function(){ $('#route-form').submit(); }, 500); // Try to submit form after timeout
return false;
} else {
// Continue with location found...
}
});
export default (condition: Function, interval = 1000) =>
new Promise((resolve) => {
const runner = () => {
const timeout = setTimeout(runner, interval);
if (condition()) {
clearTimeout(timeout);
resolve(undefined);
return;
}
};
runner();
});
class App extends React.Component {
componentDidMount() {
this.processToken();
}
processToken = () => {
try {
const params = querySearch(this.props.location.search);
if('accessToken' in params){
this.setOrderContext(params);
this.props.history.push(`/myinfo`);
}
} catch(ex) {
console.log(ex);
}
}
setOrderContext (params){
//this action calls a reducer and put the token in session storage
this.props.userActions.processUserToken({data: {accessToken:params.accessToken}});
}
render() {
return (
<Switch>
//myinfo component needs accessToken to retrieve my info
<Route path="/myInfo" component={InofUI.App} />
</Switch>
);
}
And then inside InofUI.App
componentDidMount() {
this.retrieveMyInfo();
}
retrieveMyInfo = async () => {
await this.untilTokenIsSet();
const { location, history } = this.props;
this.props.processUser(location, history);
}
untilTokenIsSet= () => {
const poll = (resolve) => {
const { user } = this.props;
const { accessToken } = user;
console.log('getting accessToken', accessToken);
if (accessToken) {
resolve();
} else {
console.log('wating for token .. ');
setTimeout(() => poll(resolve), 100);
}
};
return new Promise(poll);
}
Try using setInterval and clearInterval like this...
var current_latlng = null;
function gpsSuccess(pos) {
//console.log('gpsSuccess');
if (pos.coords) {
lat = pos.coords.latitude;
lng = pos.coords.longitude;
} else {
lat = pos.latitude;
lng = pos.longitude;
}
current_latlng = new google.maps.LatLng(lat, lng);
}
watchId = navigator.geolocation.watchPosition(gpsSuccess,
gpsFail, {
timeout: 5000,
maximumAge: 300000
});
$('#route-form').submit(function (event) {
// User submits form, we need their location...
// Checks status every half-second
var watch = setInterval(task, 500)
function task() {
if (current_latlng != null) {
clearInterval(watch)
watch = false
return callback()
} else {
toastMessage('Waiting for your location...');
}
}
function callback() {
// Continue on with location found...
}
});
This accepts any function, even if it's async, and when it evaluates to a truthy value (checking every quarter-second by default), resolves to it.
function waitFor(condition, step = 250, timeout = Infinity) {
return new Promise((resolve, reject) => {
const now = Date.now();
let running = false;
const interval = setInterval(async () => {
if (running) return;
running = true;
const result = await condition();
if (result) {
clearInterval(interval);
resolve(result);
} else if (Date.now() - now >= timeout * 1000) {
clearInterval(interval);
reject(result);
}
running = false;
}, step);
});
}
Example
example();
async function example() {
let foo = 'bar';
setTimeout(() => foo = null, 2000);
console.log(`foo === "${foo}"`);
await waitFor(() => foo === null);
console.log('2 seconds have elapsed.');
console.log(`foo === ${foo}`);
}
function waitFor(condition, step = 250, timeout = Infinity) {
return new Promise((resolve, reject) => {
const now = Date.now();
let running = false;
const interval = setInterval(async () => {
if (running) return;
running = true;
const result = await condition();
if (result) {
clearInterval(interval);
resolve(result);
} else if (Date.now() - now >= timeout * 1000) {
clearInterval(interval);
reject(result);
}
running = false;
}, step);
});
}