Wait until a condition is true?
I'm using navigator.geolocation.watchPosition
in JavaScript, and I want a way to deal with the possib开发者_如何学JAVAility 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);
});
}
精彩评论