{"action":"created","issue":{"url":"https://api.github.com/repos/w3c/sensors/issues/88","repository_url":"https://api.github.com/repos/w3c/sensors","labels_url":"https://api.github.com/repos/w3c/sensors/issues/88/labels{/name}","comments_url":"https://api.github.com/repos/w3c/sensors/issues/88/comments","events_url":"https://api.github.com/repos/w3c/sensors/issues/88/events","html_url":"https://github.com/w3c/sensors/issues/88","id":138788088,"number":88,"title":"Add solution for one-shot readings","user":{"login":"tobie","id":3392,"avatar_url":"https://avatars.githubusercontent.com/u/3392?v=3","gravatar_id":"","url":"https://api.github.com/users/tobie","html_url":"https://github.com/tobie","followers_url":"https://api.github.com/users/tobie/followers","following_url":"https://api.github.com/users/tobie/following{/other_user}","gists_url":"https://api.github.com/users/tobie/gists{/gist_id}","starred_url":"https://api.github.com/users/tobie/starred{/owner}{/repo}","subscriptions_url":"https://api.github.com/users/tobie/subscriptions","organizations_url":"https://api.github.com/users/tobie/orgs","repos_url":"https://api.github.com/users/tobie/repos","events_url":"https://api.github.com/users/tobie/events{/privacy}","received_events_url":"https://api.github.com/users/tobie/received_events","type":"User","site_admin":false},"labels":[],"state":"open","locked":false,"assignee":null,"milestone":{"url":"https://api.github.com/repos/w3c/sensors/milestones/2","html_url":"https://github.com/w3c/sensors/milestones/Level%201","labels_url":"https://api.github.com/repos/w3c/sensors/milestones/2/labels","id":1103487,"number":2,"title":"Level 1","description":"Work that needs to be completed for the Level 1 version of the spec.","creator":{"login":"tobie","id":3392,"avatar_url":"https://avatars.githubusercontent.com/u/3392?v=3","gravatar_id":"","url":"https://api.github.com/users/tobie","html_url":"https://github.com/tobie","followers_url":"https://api.github.com/users/tobie/followers","following_url":"https://api.github.com/users/tobie/following{/other_user}","gists_url":"https://api.github.com/users/tobie/gists{/gist_id}","starred_url":"https://api.github.com/users/tobie/starred{/owner}{/repo}","subscriptions_url":"https://api.github.com/users/tobie/subscriptions","organizations_url":"https://api.github.com/users/tobie/orgs","repos_url":"https://api.github.com/users/tobie/repos","events_url":"https://api.github.com/users/tobie/events{/privacy}","received_events_url":"https://api.github.com/users/tobie/received_events","type":"User","site_admin":false},"open_issues":11,"closed_issues":12,"state":"open","created_at":"2015-05-10T07:22:10Z","updated_at":"2016-03-06T16:47:57Z","due_on":null,"closed_at":null},"comments":3,"created_at":"2016-03-06T12:37:44Z","updated_at":"2016-03-06T19:44:20Z","closed_at":null,"body":"_Warning: all example code below is for illustrative purposes only, none of the below works or is spec'ed as is._\r\n\r\nSo here's the crux of the issue. We have the following requirements:\r\n\r\n* we want to observe sensors for change or at regular intervals (for example to plot someone's itinerary on a map), and we want the API for that to be super simple, e.g.:\r\n    \r\n    ```js\r\n    let sensor = new GeolocationSensor({ accuracy: \"high\" });\r\n    sensor.onreading = event => updatePosition(event.reading.coords));\r\n    ```\r\n* we sometimes want a unique data point from a sensor, for example to figure out someone's current coordinates and have no need for monitoring that sensor:\r\n    \r\n    ```js\r\n    let sensor = new GeolocationSensor({ accuracy: \"high\" });\r\n    sensor.read().then(reading => findAddress(reading.coords));\r\n    ```\r\n* we sometimes want a unique data point from a sensor, but only if it's been cached before (we care about battery life):\r\n    \r\n    ```js\r\n    let sensor = new GeolocationSensor({ accuracy: \"high\" });\r\n    sensor.readFromCache().then(reading => findAddress(reading.coords));\r\n    ```\r\n\r\nAs we're essentially going for battery/CPU savings when calling the `.read()` and `.readFromCache()` methods, we certainly don't want to start observing the sensor in that case. So the examples above, while they look nice and nifty don't really show the full picture; we need a way to get only one reading without triggering multiple reads afterwards.\r\n\r\nWe have a number of solutions here:\r\n\r\n1. We have an instance `read()` (and readFromCache()`) method. Calling it gets a single reading and immediately stops sensor polling: \r\n\r\n    ```js\r\n    let sensor = new GeolocationSensor({ accuracy: \"high\" });\r\n    sensor.read().then(reading => findAddress(reading.coords));\r\n    ```\r\n    \r\n    What happens in the following scenario, though?\r\n    \r\n    ```js\r\n    let sensor = new GeolocationSensor({ accuracy: \"high\" });\r\n    sensor.read().then(reading => findAddress(reading.coords));\r\n    sensor.onreading = event => displayMap(event.reading.coords);\r\n    ```\r\n    \r\n    Is a single reading event emitted? None? Should setting `onreading` throw?\r\n\r\n    **Pros:**\r\n    \r\n    * Both one shot and event based APIs look really nice when used as demoed.\r\n    * The `read()` method can be added at a later stage though it will really feel bolted on.\r\n    \r\n    **Cons:**\r\n\r\n    * Once `read()` has been called the sensor object is in a completely different state than it usually is, and that might not be obvious at all to someone using it. For example, one can start listening to the sensor after having called `read()` and won't receive any notifications.\r\n    * Won't let you pass around sensor instances without triggering hardware activity (which means that a future discovery API might need to rely on lightweight sensor-like objects instead of just using those).\r\n\r\n2. Sensors do **not** start polling automatically when instantiated. We have instance `startObserving()`, `readOnce()` (and readFromCache()`) methods.\r\n    \r\n    To read once:\r\n    ```js\r\n    let sensor = new GeolocationSensor({ accuracy: \"high\" });\r\n    sensor.read().then(reading => findAddress(reading.coords));\r\n    ```\r\n    To react to sensor updates:\r\n    ```js\r\n    let sensor = new GeolocationSensor({ accuracy: \"high\" });\r\n    sensor.startObserving();\r\n    sensor.onreading = event => displayMap(event.reading.coords);\r\n    ```\r\n\r\n    **Pros:**\r\n    \r\n    * There is no ambiguity. If `startObserving()` hasn't been called, no events are fired.\r\n    * Allows passing around sensor instances without triggering hardware activity (simplifies a future discovery API).\r\n    \r\n    **Cons:**\r\n\r\n    * Event-based solution a bit more wordy than solution one above.\r\n    * This needs to be thought out from the start or risk pretty serious backwards compatibility issues.\r\n\r\n3. While sensors still start automatically polling when instantiated, we have class methods for one-shot readings:\r\n    \r\n    ```js\r\n    GeolocationSensor.read({ accuracy: \"high\" }).then(reading => findAddress(reading.coords));\r\n    ```\r\n\r\n    **Pros:**\r\n    \r\n    * Both one shot and event-based APIs look rather nice.\r\n    * The one-shot `read()` method can be added at a later stage without causing backwards compatibility issues.\r\n\r\n    \r\n    **Cons:**\r\n\r\n    * Event-based solution a bit more wordy than solution one above.\r\n    * Won't let you pass around sensor instances without triggering hardware activity (which means that a future discovery API might need to rely on lightweight sensor-like objects instead of just using those).\r\n    * Feels sort of awkward when you have multiple sensors of a same type:\r\n        \r\n        ```js\r\n        ProximitySensor.read({\r\n            position: \"bottom-left\",\r\n            direction: \"rear\"\r\n        }).then(/* ... */);\r\n        ```\r\n\r\nWhich we can sort of summarize as follows:\r\n\r\n<table>\r\n    <thead>\r\n        <tr>\r\n            <th></th>\r\n            <th>Solution 1</th>\r\n            <th>Solution 2</th>\r\n            <th>Solution 3</th>\r\n        </tr>\r\n    </thead>\r\n    <tbody>\r\n        <tr>\r\n            <th>One-shot API</th>\r\n            <td>\r\n<pre>s = new Sensor({…});\r\ns.read().then…</pre>\r\n            </td>\r\n            <td>\r\n<pre>s = new Sensor({…});\r\ns.read().then…</pre>\r\n            </td>\r\n            <td>\r\n<pre>Sensor.read({…}).then…</pre>\r\n            </td>\r\n        </tr>\r\n        <tr>\r\n            <th><abbr title=\"Developer Experience\">DX</abbr></th>\r\n            <td>:star::star::star::star::star:</td>\r\n            <td>:star::star::star::star::star:</td>\r\n            <td>:star::star::star:</td>\r\n        </tr>\r\n        <tr>\r\n            <th>Event-based API</th>\r\n            <td>\r\n<pre>s = new Sensor({…});\r\ns.onreading = e => …</pre>\r\n            </td>\r\n            <td>\r\n<pre>s = new Sensor({…});\r\ns.startObserving();\r\ns.onreading = e => …</pre>\r\n            </td>\r\n            <td>\r\n<pre>s = new Sensor({…});\r\ns.onreading = e => …</pre>\r\n            </td>\r\n        </tr>\r\n        <tr>\r\n            <th><abbr title=\"Developer Experience\">DX</abbr></th>\r\n            <td>:star::star::star:</td>\r\n            <td>:star::star::star:</td>\r\n            <td>:star::star::star::star::star:</td>\r\n        </tr>\r\n        <tr>\r\n            <th>Nasty side effects</th>\r\n            <td>:skull::skull::skull::skull::skull:</td>\r\n            <td>:star::star::star::star::star:</td>\r\n            <td>:star::star::star::star::star:</td>\r\n        </tr>\r\n        <tr>\r\n            <th>Plays well w/ discovery API</th>\r\n            <td>:star:</td>\r\n            <td>:star::star::star::star::star:</td>\r\n            <td>:star:</td>\r\n        </tr>\r\n        <tr>\r\n            <th>Total</th>\r\n            <td>:star::star::skull:</td>\r\n            <td>:star::star::star::star::star:</td>\r\n            <td>:star::star::star::star:</td>\r\n        </tr>\r\n    </tbody>\r\n</table>\r\n\r\nI'd love to hear's people's opinion here. I initially thought about punting decision around this until a V2, but realized that some of the solutions aren't really backwards compatible, so would rather figure out something now."},"comment":{"url":"https://api.github.com/repos/w3c/sensors/issues/comments/192972157","html_url":"https://github.com/w3c/sensors/issues/88#issuecomment-192972157","issue_url":"https://api.github.com/repos/w3c/sensors/issues/88","id":192972157,"user":{"login":"anssiko","id":765510,"avatar_url":"https://avatars.githubusercontent.com/u/765510?v=3","gravatar_id":"","url":"https://api.github.com/users/anssiko","html_url":"https://github.com/anssiko","followers_url":"https://api.github.com/users/anssiko/followers","following_url":"https://api.github.com/users/anssiko/following{/other_user}","gists_url":"https://api.github.com/users/anssiko/gists{/gist_id}","starred_url":"https://api.github.com/users/anssiko/starred{/owner}{/repo}","subscriptions_url":"https://api.github.com/users/anssiko/subscriptions","organizations_url":"https://api.github.com/users/anssiko/orgs","repos_url":"https://api.github.com/users/anssiko/repos","events_url":"https://api.github.com/users/anssiko/events{/privacy}","received_events_url":"https://api.github.com/users/anssiko/received_events","type":"User","site_admin":false},"created_at":"2016-03-06T19:44:20Z","updated_at":"2016-03-06T19:44:20Z","body":"Good points from @rwaldron.\r\n\r\nJust noting the `MessagePort` has this interesting (not very intuitive IMO) behaviour:\r\n\r\n>... when using `addEventListener()`, the `start()` method must also be invoked. When using `onmessage`, the call to `start()` is implied.\r\n\r\nApplying that pattern to the solution 1 the call to `start()` would be implied with `onreading`. We'd need to `start()` when using `addEventListener()` though.\r\n\r\nSeems more clear to require `start()` for both `onreading` and `addEventListener('reading', ...)`, thus the solutions 1 and 2 should actually be the same. I'd probably name the method simply `start()`.\r\n\r\nAlso wondering if there are good use cases for explicit `pause()` and `resume()` or `stop()` and how would the solution 1+2 compare with 3 in that case."}}