6
\$\begingroup\$

In a plugin that I am currently authoring, I am using a function to check whether the device provides gyroscopic data. Although intuitively this can be easily resolved by checking the value of window.DeviceOrientationEvent, i.e.:

if (window.DeviceOrientationEvent) {
    // Ok, device orientation event listening is supported!
}

…this statement also resolves true when on a device with no functional gyroscope, i.e. Google Chrome on a laptop. The event data returned for alpha, beta and gamma are, however, null. Basically, the window.DeviceOrientationEvent does not tell me if there is gyroscopic data available, but only informs me if the browser/user-agent supports the event.

Therefore, not only do I have to check for support for the event, but also have to cross verify the value of alpha, beta or gamma such that they are not null. The best way I could check is using promises, which I feel a bit cumbersome and long-winded:

var check = {
    // Does it have a working gyroscope?
    gyroscope: function() {
        var d = new $.Deferred(),
            handler = function(e) {
                d.resolve({
                    alpha: e.alpha,
                    beta: e.beta,
                    gamma: e.gamma
                });
                // Listen to device orientation once and remove listener immediately
                window.removeEventListener('deviceorientation', handler, false);
            };
        window.addEventListener('deviceorientation', handler, false);
        return d;
    }
}

// Wait for gyroscope data
$.when(check.gyroscope()).done(function(r) {
    if(r.alpha === null && r.beta === null && r.gamma === null) {
        console.log('Device has no functional gyroscope.');
    } else {
        console.log('Device has functional gyroscope.')
    }
});

I am wondering if there is a cleaner way to write this code?

\$\endgroup\$
  • 1
    \$\begingroup\$ For reference, here's a library that seems to do much of the same, and is cross-browser (i.e. supports events with vendor prefixes) \$\endgroup\$ – Flambino May 8 '15 at 22:45
  • \$\begingroup\$ @Flambino Wow, didn't know that library existed — should've googled a tad bit harder ;) \$\endgroup\$ – Terry May 8 '15 at 23:47
  • \$\begingroup\$ Well, I don't know if it's any good - just stumbled across it. I skimmed the code though, and it seems pretty neat and well-written. Figured you'd be interested. \$\endgroup\$ – Flambino May 9 '15 at 7:29
3
\$\begingroup\$
  • I imagine you're still checking window.DeviceOrientationEvent? Because if that doesn't exist, you can presumably skip everything else.

  • When using $.Deferred you should always return its .promise() - not the deferred object itself. The deferred object itself is "read/write" (i.e. you can call resolve and reject on it), while the promise it creates is "read-only" which is what you want to pass on to other code.

  • You don't need $.when unless you're waiting for several promises to resolve. You can simple call promise.done() (or .then or .fail or .always)

  • Since the idea is get a yes/no answer, I'd simply use the resolve/reject callbacks of the deferred and do all the checking in one place.

  • I don't know if it's relevant, but checking strictly against null will give you a false positive if alpha etc.are actually completely undefined. It might be better to check typeof alpha === 'number'.

  • Of course, you don't really need promises; a simple callback would do.

For instance:

var check = {
  gyroscope: function (callback) {
    function handler(event) {
      var hasGyro = typeof event.alpha === 'number'
                 && typeof event.beta  === 'number'
                 && typeof event.gamma === 'number';
      window.removeEventListener('deviceorientation', handler, false);
      callback(hasGyro);
    }
    window.addEventListener('deviceorientation', handler, false);
  }
};

check.gyroscope(function (hasGyroscope) {
  if(hasGyroscope) {
    console.log('Device has functional gyroscope.')
  } else {
    console.log('Device has no functional gyroscope.');
  }
});

If you do want to use $.Deferred, I'd recommend something like:

var check = {
  gyroscope: function () {
    var deferred = $.Deferred();

    function handler(event) {
      var hasGyro = typeof event.alpha === 'number'
                 && typeof event.beta  === 'number'
                 && typeof event.gamma === 'number';
      window.removeEventListener('deviceorientation', handler, false);
      hasGyro ? deferred.resolve() : deferred.reject();
    }

    window.addEventListener('deviceorientation', handler, false);
    return deferred.promise();
  }
};

check.gyroscope().then(function () {
  console.log('Device has functional gyroscope.')
}, function () {
  console.log('Device has no functional gyroscope.');
});
\$\endgroup\$
  • \$\begingroup\$ Thanks for the useful input! I have to admit that I have unintentionally left out some important information—$.when() was used to check for other promises, too, and two of which are AJAX calls; and that I did do a basic window.DeviceOrientationEvent check, but left it out when I was reducing the code. And thanks for pointing out that I forgot to return the promise... and surprised that it still works when I forget to do so. \$\endgroup\$ – Terry May 9 '15 at 10:14
  • \$\begingroup\$ I learn from this, thanks. But something puzzles me: why to have a "double envelope" var check = { gyroscope: function() {...} }; instead of something more simple like var checkGyro = function() {...}; ? \$\endgroup\$ – cFreed May 9 '15 at 12:47
  • 1
    \$\begingroup\$ @cFreed True, a plain function would work just as well. I just assumed that there might be other functions in that check namespace/object which were just left out in the question. So I just kept that structure intact, and focussed on the content. \$\endgroup\$ – Flambino May 9 '15 at 13:10

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

Not the answer you're looking for? Browse other questions tagged or ask your own question.