31

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...
});
Lightness Races in Orbit
  • 378,754
  • 76
  • 643
  • 1,055
Richard
  • 31,629
  • 29
  • 108
  • 145

9 Answers9

42

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

Lightbeard
  • 4,011
  • 10
  • 49
  • 59
29

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 !');
});
user247702
  • 23,641
  • 15
  • 110
  • 157
  • don't forget the `);` at the end of the `waitfor()` function – Gianfranco P. Jan 17 '14 at 09:58
  • 2
    But if there's more code below the final (aka the initial) waitfor call, won't that code continue to execute, until it pauses because it's time to run the first scheduled setTimeout call? initially waitfor is just a function that sets up a setTimeout call and then returns. your code will *watch* until the value changes, but it won't *block* until the value changes. – Ben Wheeler Feb 05 '15 at 23:26
15

This is precisely what promises were invented and implemented (since OP asked his question) for.

See all of the various implementations, eg promisejs.org

Ben Wheeler
  • 6,788
  • 2
  • 45
  • 55
  • 3
    this is the only right answer to the question and it is really worrysome that it had 0 votes until now – Hans Westerbeek Oct 01 '15 at 09:56
  • 19
    @HansWesterbeek probably because the concept of promises is a broad subject. I already had a feeling that I should use a promise, but this answer is too vague to help me. – user247702 Nov 12 '15 at 14:24
14

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.

Nicole
  • 32,841
  • 11
  • 75
  • 101
8

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...
    }
});
Chris Pickett
  • 2,822
  • 1
  • 14
  • 7
2
export default (condition: Function, interval = 1000) =>
  new Promise((resolve) => {
    const runner = () => {
      const timeout = setTimeout(runner, interval);

      if (condition()) {
        clearTimeout(timeout);
        resolve(undefined);
        return;
      }
    };

    runner();
  });
Kirk Strobeck
  • 17,984
  • 20
  • 75
  • 114
0
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);
    } 
Sajjad
  • 133
  • 1
  • 10
0

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...
    }
});
openwonk
  • 14,023
  • 7
  • 43
  • 39
0

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);
  });
}
GirkovArpa
  • 4,427
  • 4
  • 14
  • 43