{"action":"closed","issue":{"url":"https://api.github.com/repos/ubolonton/js-csp/issues/77","repository_url":"https://api.github.com/repos/ubolonton/js-csp","labels_url":"https://api.github.com/repos/ubolonton/js-csp/issues/77/labels{/name}","comments_url":"https://api.github.com/repos/ubolonton/js-csp/issues/77/comments","events_url":"https://api.github.com/repos/ubolonton/js-csp/issues/77/events","html_url":"https://github.com/ubolonton/js-csp/issues/77","id":124660320,"number":77,"title":"Scoped cancellable goroutines, try-catch-finally proposal","user":{"login":"machty","id":81818,"avatar_url":"https://avatars.githubusercontent.com/u/81818?v=3","gravatar_id":"","url":"https://api.github.com/users/machty","html_url":"https://github.com/machty","followers_url":"https://api.github.com/users/machty/followers","following_url":"https://api.github.com/users/machty/following{/other_user}","gists_url":"https://api.github.com/users/machty/gists{/gist_id}","starred_url":"https://api.github.com/users/machty/starred{/owner}{/repo}","subscriptions_url":"https://api.github.com/users/machty/subscriptions","organizations_url":"https://api.github.com/users/machty/orgs","repos_url":"https://api.github.com/users/machty/repos","events_url":"https://api.github.com/users/machty/events{/privacy}","received_events_url":"https://api.github.com/users/machty/received_events","type":"User","site_admin":false},"labels":[],"state":"closed","locked":false,"assignee":null,"milestone":null,"comments":1,"created_at":"2016-01-03T17:29:26Z","updated_at":"2016-02-28T16:24:59Z","closed_at":"2016-02-28T16:24:59Z","body":"I'd like to propose some related features/improvements to js-csp that touches on pre-existing discussions on promise interactions (#13) and error handling (#14). These features are:\r\n\r\n1. Consider scoping the lifetime of goroutines to parent goroutines when `yield go()` is used\r\n2. Expose a `.kill()` or `.terminate()` fn on the return value of `go()` to make goroutines externally cancellable\r\n3. `yield`ing a promise blocks on that promise, and leverages try-catch-finally semantics\r\n\r\n## Scoping goroutine lifetimes to parents\r\n\r\nIn [node-csp](https://github.com/olahol/node-csp) (a different CSP implementation than js-csp), if a goroutine internally does a `yield go(fn *)`, this creates a subprocess that terminates when the parent process terminates. This seems like a convenient feature, with afaik the only alternative in js-csp (and the pattern established in Go lang) to pass in a channel to the inner goroutine that signals when to terminate, which must then presumably be used in a prioritized select, etc etc. And in `node-csp` if you _don't_ want to scope the lifetime of inner goroutines to a parent goroutine, you simply omit the `yield`. Would this possibly be a welcome improvement to `js-csp`, and if not, what are the conceptual arguments against it?\r\n\r\n## Externally kill-able routines\r\n\r\nIt would be nice to have a direct way of externally terminating a goroutine. Right now in `js-csp`, `go()` returns a channel that emits the value returned by the goroutine, which you can `close`, but it won't actually stop the goroutine. If we had this, we'd have a pretty nice pattern for cancellable tasks that don't have to constantly check at every step of the way (via selects() or checking a flag or whatever the approach) whether they should keep going. For example:\r\n\r\n```js\r\n// pretend `go` returns a Process handle instead of a channel\r\nlet proc = go(function * () {\r\n  setState({ word: \"Connecting in a moment...\" });\r\n\r\n  yield csp.timeout(500);  \r\n\r\n  let ws;\r\n  try {\r\n    ws = new WebSocket(\"http://wat.com\");\r\n    setState({ word: \"Connecting now...\" });\r\n    \r\n    yield makeOnConnectChannel(ws);\r\n\r\n    setState({ word: \"Connected!\" });\r\n\r\n    let chan = makeMessageChannel(ws);\r\n    while (true) {\r\n      let value = yield chan;\r\n      setState({ word: `Got message: ${value}` });\r\n    }\r\n  } finally {\r\n    ws.close();\r\n    setState({ word: \"Disconnected!\" });\r\n    // ...and any other cleanup\r\n    // this block would be invoked upon termination\r\n    // (a lesser known fact about generators is that calling .throw/.return\r\n    // on them will still run finally blocks and even let you yield additional values before closing)\r\n  }\r\n});\r\n\r\n// later, after user presses a button, or leaves a route\r\nproc.kill();\r\n```\r\n\r\nWhile I understand the value of keeping the primitives/rules simple for managing processes (i.e. use channels for everything), the nice thing about the above is:\r\n\r\n1. It has all the niceties of the proposed async/await syntax, but with the ability to cancel (which promises won't be able to do for quite a long time)\r\n2. You avoid having to litter your generator function with all sorts of \"should i keep going?\" checks\r\n3. `try/catch/finally` can idiomatically be used for cleanup\r\n\r\n## Block on yielded promises\r\n\r\nThis pattern also touches upon some of the discussion in #13; here's an example that uses try-catch-finally under the assumption that `yield`ing promises, blocks on them:\r\n\r\n```js\r\ngo(function * () {\r\n  let ajaxRequest = ajax(\"/wat.json\");\r\n  try {\r\n    let value = yield ajaxRequest.promise(\"/wat.json\");\r\n    setState({ message: `got value: ${value}` });\r\n  } catch(e) { \r\n    setState({ message: \"an error occurred\" });\r\n  } finally {\r\n    ajaxRequest.close();\r\n  }\r\n});\r\n```\r\n\r\nIf the ajax promise rejects, then it'll run the catch and finally handlers for error handling and cleanup. If the process is externally cancelled before the ajax returns, then the `finally` block closes the ajax request (again, something you can't do with promises).\r\n\r\n## Conclusion\r\n\r\nI've implemented the above semantics in my app (with my a own custom and much less feature completion impl of CSP) and it's greatly cleaned up the logic in a complex asynchronous loop, all whilst avoiding the grunt work / boilerplate of checking cancellation channels whilst leveraging try-catch-finally for error handling and cleanup. So far, it seems like a very clean and powerful pattern to me, so I'd love to find out if there are any compelling reasons not to adopt something like this in `js-csp`."}}