{"action":"created","issue":{"url":"https://api.github.com/repos/tc39/proposal-observable/issues/119","repository_url":"https://api.github.com/repos/tc39/proposal-observable","labels_url":"https://api.github.com/repos/tc39/proposal-observable/issues/119/labels{/name}","comments_url":"https://api.github.com/repos/tc39/proposal-observable/issues/119/comments","events_url":"https://api.github.com/repos/tc39/proposal-observable/issues/119/events","html_url":"https://github.com/tc39/proposal-observable/issues/119","id":192111710,"number":119,"title":"SubscriptionObserver should never throw, just like Promise","user":{"login":"jhusain","id":466863,"avatar_url":"https://avatars.githubusercontent.com/u/466863?v=3","gravatar_id":"","url":"https://api.github.com/users/jhusain","html_url":"https://github.com/jhusain","followers_url":"https://api.github.com/users/jhusain/followers","following_url":"https://api.github.com/users/jhusain/following{/other_user}","gists_url":"https://api.github.com/users/jhusain/gists{/gist_id}","starred_url":"https://api.github.com/users/jhusain/starred{/owner}{/repo}","subscriptions_url":"https://api.github.com/users/jhusain/subscriptions","organizations_url":"https://api.github.com/users/jhusain/orgs","repos_url":"https://api.github.com/users/jhusain/repos","events_url":"https://api.github.com/users/jhusain/events{/privacy}","received_events_url":"https://api.github.com/users/jhusain/received_events","type":"User","site_admin":false},"labels":[{"id":206344306,"url":"https://api.github.com/repos/tc39/proposal-observable/labels/bug","name":"bug","color":"fc2929","default":true}],"state":"open","locked":false,"assignee":null,"assignees":[],"milestone":null,"comments":18,"created_at":"2016-11-28T20:41:42Z","updated_at":"2016-11-30T03:38:32Z","closed_at":null,"body":"Observables, like Promises, can be either multicast or unicast. This is an implementation detail, should not be observable. In other words, it should be possible to switch an Observable from unicast to multicast without changing any code in any of the Observers of that Observable.\r\n\r\nGiven that design constraint it seems necessary that **SubscriptionObservers, like Promises, must swallow errors.** Allowing errors thrown from Observers to propagate can leak details of whether an Observable is unicast or multicast. To demonstrate this issue, consider the following Subject implementation: \r\n\r\n```js\r\nclass Subject extends Observable {\r\n  constructor() {\r\n    super(observer => {\r\n      this._observers.add(observer);\r\n    });\r\n    this._observers = new Set();\r\n    return () => this._observers.delete(observer);\r\n  }\r\n\r\n  _multicast(msg, value) {\r\n    const observers = Array.from(this._observers);\r\n    for(let observer of observers) {\r\n      observer[msg](value);\r\n    }\r\n    return undefined;\r\n  }\r\n\r\n  next(value) {\r\n    return this._multicast(\"next\", value);\r\n  }\r\n  error(error) {\r\n    return this._multicast(\"error\", error);\r\n  }\r\n  complete(value) {\r\n    return this._multicast(\"complete\", value);\r\n  }\r\n}\r\n```\r\n\r\nNote in the code above that if an observer throws from either next, error, or complete, then none of the remaining observers receive their notifications. Of course, we can guard against this by surrounding each notification in a catch block, capturing any error, and rethrowing it later.\r\n\r\n```js\r\n  _multicast(msg, value) {\r\n    const observers = Array.from(this._observers);\r\n    let error;\r\n    for(let observer of observers) {\r\n      try {\r\n        observer[msg](value);\r\n      }\r\n      catch(e) {\r\n        error = e;\r\n      }\r\n    }\r\n\r\n    if (error) {\r\n      throw error;\r\n    }\r\n    return undefined;\r\n  }\r\n```\r\n\r\nUnforunately this only works if there is only _one_ error. What if multiple observers error? We could aggregate all observer errors into a CompositeError like so:\r\n\r\n```js\r\n  _multicast(msg, value) {\r\n    const observers = Array.from(this._observers);\r\n    let errors = [];\r\n    for(let observer of observers) {\r\n      try {\r\n        observer[msg](value);\r\n      }\r\n      catch(e) {\r\n        errors.push(e);\r\n      }\r\n    }\r\n\r\n    if (errors.length) {\r\n      throw new CompositeError(errors);\r\n    }\r\n    return undefined;\r\n  }\r\n```\r\n\r\nUnfortunately now we have a new leak in the abstraction: a new error type which is only thrown from multi-cast Observables.  This means that code that captures a particular type of error may fail when an Observable is switched from unicast to multicast:\r\n\r\n```js\r\ntry {\r\n  observable.subscribe({ next(v) { throw TypeError })\r\n} catch(e) {\r\n  // worked before when observable was unicast,\r\n  // but when switched to multicast a CompositeError thrown instead\r\n  if (e instanceof MyCustomError) {\r\n    //...\r\n  }\r\n}\r\n```\r\n\r\nOnce again we can see that the interface changes when moving from a uni-cast to multi-cast Observable, and the implementation details leak out.\r\n\r\nThe only way I can see to cleanly abstract over multi-cast and uni-cast Observables is to _never_ mix push and pull notification. I propose the following simple rule: subscribe never throws. All errors are caught, and if there is no method to receive the push notification that the error occurred, the error is swallowed."},"comment":{"url":"https://api.github.com/repos/tc39/proposal-observable/issues/comments/263774728","html_url":"https://github.com/tc39/proposal-observable/issues/119#issuecomment-263774728","issue_url":"https://api.github.com/repos/tc39/proposal-observable/issues/119","id":263774728,"user":{"login":"jhusain","id":466863,"avatar_url":"https://avatars.githubusercontent.com/u/466863?v=3","gravatar_id":"","url":"https://api.github.com/users/jhusain","html_url":"https://github.com/jhusain","followers_url":"https://api.github.com/users/jhusain/followers","following_url":"https://api.github.com/users/jhusain/following{/other_user}","gists_url":"https://api.github.com/users/jhusain/gists{/gist_id}","starred_url":"https://api.github.com/users/jhusain/starred{/owner}{/repo}","subscriptions_url":"https://api.github.com/users/jhusain/subscriptions","organizations_url":"https://api.github.com/users/jhusain/orgs","repos_url":"https://api.github.com/users/jhusain/repos","events_url":"https://api.github.com/users/jhusain/events{/privacy}","received_events_url":"https://api.github.com/users/jhusain/received_events","type":"User","site_admin":false},"created_at":"2016-11-30T03:38:32Z","updated_at":"2016-11-30T03:38:32Z","body":"@zenparsing with respect to what subscriptions  do, it's not clear to me that allowing subscriptions to throw interferes with our ability to cleanly abstract over unicast and multicast Observables. Can you think of an example where this would be problematic?"}}
{"actor":{"display_login":"jhusain"}}