This is an archived snapshot of W3C's public bugzilla bug tracker, decommissioned in April 2019. Please see the home page for more details.
http://heycam.github.io/webidl/#es-operations The spec for operations says: # If O has a return type that is a promise type, then: # 1. Let reject be the initial value of %Promise%.reject. # 2. Return the result of calling reject with %Promise% as the this # object and the exception as the single argument value. This is bad practice, IMHO, as discussed in detail here: https://github.com/domenic/promises-unwrapping/issues/24 If I want my API to reject the promise, then I'll reject the promise. If I want it to throw an exception, then I should be able to throw an exception. In particular, if an API is called with bad arguments, then the exception should throw immediately, just like if it's name was typoed. The behaviour of: foo.bar(-1); // only accepts positive numbers ...should be the same as the behaviour of: foo.baz(1); // there's no "baz" method, it's called "bar"
> This is bad practice, IMHO Others disagree, right? Note that the current webidl language has shipping implementations and specifications depending on it, by the way.
Yes, there seems to be different opinions of what makes good API design here. In particular, people have expressed the opinion that having functions that can fail both synchronously and asynchronously means that developers that want to do error handling now have to "catch" errors in two different ways. And that this is a bad thing. Personally I've found this argument fairly convincing. The strongest counter argument to me has been that some errors are due to runtime behavior and so makes a lot of sense to want to handle during runtime code. For example IO errors or users answering "no" in a security dialog. Other errors are due to plain old bugs in the code which can make a certain line of code fail every single time. Such as forgetting to include all arguments, or using the wrong type for an argument. It's good if we can surface such errors as quickly as possible, and in a way that makes debugging as easy as possible. However as long as developer tools gain knowledge about promises and can surface rejections as well as they traditionally have surfaced exceptions, then it seems like that takes care of this concern.
(In reply to Jonas Sicking from comment #2) > Yes, there seems to be different opinions of what makes good API design here. > > In particular, people have expressed the opinion that having functions that > can fail both synchronously and asynchronously means that developers that > want to do error handling now have to "catch" errors in two different ways. > And that this is a bad thing. Personally I've found this argument fairly > convincing. > > The strongest counter argument to me has been that some errors are due to > runtime behavior and so makes a lot of sense to want to handle during > runtime code. For example IO errors or users answering "no" in a security > dialog. > > Other errors are due to plain old bugs in the code which can make a certain > line of code fail every single time. Such as forgetting to include all > arguments, or using the wrong type for an argument. It's good if we can > surface such errors as quickly as possible, and in a way that makes > debugging as easy as possible. Countering this, I've written algos that could detect *some* of these types of errors syncly, but others had to wait for async stuff to come in. It's not obvious a priori which types of mistakes fall into which category, as it's intimately related to the details of the algorithm. Having some TypeErrors and similar things happen as thrown exceptions and others happen as rejected promises is just all kinds of confusing.
(In reply to Jonas Sicking from comment #2) > Yes, there seems to be different opinions of what makes good API design here. > > In particular, people have expressed the opinion that having functions that > can fail both synchronously and asynchronously means that developers that > want to do error handling now have to "catch" errors in two different ways. > And that this is a bad thing. Personally I've found this argument fairly > convincing. > > The strongest counter argument to me has been that some errors are due to > runtime behavior and so makes a lot of sense to want to handle during > runtime code. For example IO errors or users answering "no" in a security > dialog. > > Other errors are due to plain old bugs in the code which can make a certain > line of code fail every single time. Such as forgetting to include all > arguments, or using the wrong type for an argument. It's good if we can > surface such errors as quickly as possible, and in a way that makes > debugging as easy as possible. > > However as long as developer tools gain knowledge about promises and can > surface rejections as well as they traditionally have surfaced exceptions, > then it seems like that takes care of this concern. The only errors that can be reliably sync are the errors related to the received input. If you expect a positive integer and get a negative one, realising that should definitely be a synchronous operation. However, IO errors or permissions errors might be sync or async as Tab pointed. I agree with the others that having all errors rejecting the promise would make everyone's life easier. Having to handle sync and async errors would make the code harder to read and more complicated because there would be two code paths.
The platform effectively has this behavior in non-Promise cases, like XHR.send(). Success is reported via load event, some errors via error via and in some cases the method throws. And, throwing early when the state of the object is wrong sounds right to me. Requiring to use reject for that is overkill.
(In reply to Olli Pettay from comment #5) > The platform effectively has this behavior in non-Promise cases, like > XHR.send(). > Success is reported via load event, some errors via error via and in some > cases the method throws. > > And, throwing early when the state of the object is wrong sounds right to me. > Requiring to use reject for that is overkill. If you read over [1], you will see plenty of justification as to why having two separate modes of error handling is a bad idea. Basically this is a sad pattern: ``` try{ var promise = foo().then(whatever).catch( asyncErrorHandler ) }catch(e){ syncErrorHandler(e) } window.onerror = moarErrorHandling ``` I feel strongly that what we currently have specified in WebIDL is most sane. I did originally hold Hixie's position (that it's pretty easy to do all the necessary type checks through WebIDL before crossing the async boundary - and that it's traditionally not how the API's of the web work), but have been convinced we should just let the promise machinery handle the rejection. Primarily, unhandled errors are showing up in the error console of browsers, so it's trivial enough for web devs to catch and fix them during development. [1] https://github.com/domenic/promises-unwrapping/issues/24
If you read over [1], you will see plenty of justification as to why having only one mode of error handling is a bad idea too. Let's not pretend that there's any kind of consensus on this issue, either. Plenty of people commented on that issue in agreement with the idea that we shouldn't turn type checks into promise rejections. All this bug is asking for is the ability to write IDL without having to have hacks to sidestep this exception-wrapping nonsense. I would much rather be able to write: Promise<Foo> bar(); ...than have to write: interface Whatever { }; typedef (Promise<Foo> or Whatever) PromiseFoo; PromiseFoo bar(); ...which is what I'm currently forced to do to describe APIs that throw in certain cases and return promises when they don't throw.
(In reply to Ian 'Hixie' Hickson from comment #7) > If you read over [1], you will see plenty of justification as to why having > only one mode of error handling is a bad idea too. > Let's not pretend that > there's any kind of consensus on this issue, either. Plenty of people > commented on that issue in agreement with the idea that we shouldn't turn > type checks into promise rejections. True. Strong arguments are made on both sides - just saying that I personally landed on a particular side. I'm asking that those that are participating in this discussion consider the arguments very carefully because what you are asking to change here has massive implications (for both shipping and all future APIs that rely on promises). > All this bug is asking for is the ability to write IDL without having to > have hacks to sidestep this exception-wrapping nonsense. No, that's not all this bug is asking for: it has the potential to have significant implications for implementers, spec editors, and authors. If we get some in-between option, like "[throwOnInvalidInput]" or whatever, then some APIs might use this and others might not... then, as an author, you always need to check the API contract (so to wrap in try/catch); as a spec Editor, you need to decide if you want to use that and have a good justification as to why (and we risk big f'ups, like with everyone copy/pasting [NoInterfaceObject] without people understanding what it does); and as an implementer, you need to clearly separate the async and sync checks - where right now promises just deal with it (even if it's "exception-wrapping nonsense"). > I would much rather > be able to write: > > Promise<Foo> bar(); > > ...than have to write: > > interface Whatever { }; > typedef (Promise<Foo> or Whatever) PromiseFoo; > > PromiseFoo bar(); > > ...which is what I'm currently forced to do to describe APIs that throw in > certain cases and return promises when they don't throw. IIUC, what we are trying to agree on is what model we use for the Web (what the web used from the beginning of time or this new model that is currently in WebIDL - or something in between, where there is a very good reason to not have the promise handle the error through rejection). If you've managed to cleverly hack around it using a typedef doesn't really address the core problem - as whatever API you put that on may be rejected by implementers unless we get agreement on the idiom to use.
(In reply to Marcos Caceres from comment #8) > (In reply to Ian 'Hixie' Hickson from comment #7) > > I would much rather > > be able to write: > > > > Promise<Foo> bar(); > > > > ...than have to write: > > > > interface Whatever { }; > > typedef (Promise<Foo> or Whatever) PromiseFoo; > > > > PromiseFoo bar(); > > > > ...which is what I'm currently forced to do to describe APIs that throw in > > certain cases and return promises when they don't throw. > > IIUC, what we are trying to agree on is what model we use for the Web (what > the web used from the beginning of time or this new model that is currently > in WebIDL - or something in between, where there is a very good reason to > not have the promise handle the error through rejection). If you've managed > to cleverly hack around it using a typedef doesn't really address the core > problem - as whatever API you put that on may be rejected by implementers > unless we get agreement on the idiom to use. Indeed. I'll strongly recommend not implementing such a pattern to any implementors of a feature you write that for; it's clumsy and won't match the rest of the platform.
> interface Whatever { }; > typedef (Promise<Foo> or Whatever) PromiseFoo; > > PromiseFoo bar(); As an implementer, I certainly have no interest in implementing something like this. We should come to an agreement of what best practice is, encode that into WebIDL, and then stick to it. If every spec editor spec their own personal favorite API patterns then we'll get neither consistency nor quality in the web platform.
Also keep in mind that a lot of ES infrastructure is going to be created around Promises which is likely going to be optimized for functions which return rejected promises rather than throwing.
> typedef (Promise<Foo> or Whatever) PromiseFoo; > PromiseFoo bar(); I think Web IDL should disallow this, just like it disallows having overloads some of which return a Promise and some of which do not; there are no sane non-hacky use cases I can think of for it.
> If we get some in-between option, like "[throwOnInvalidInput]" or whatever, > then some APIs might use this and others might not... then, as an author, you > always need to check the API contract (so to wrap in try/catch) No, you don't. Nobody puts every method call in a try-catch today, just like nobody is going to be checking for a rejection on a promise for this kind of bug. As a developer you never need to look at what the API will do when you use it incorrectly. You only need to use it correctly. When you use it incorrectly, you should get an exception, the same way as when you call the wrong API.
(In reply to Ian 'Hixie' Hickson from comment #13) > > If we get some in-between option, like "[throwOnInvalidInput]" or whatever, > > then some APIs might use this and others might not... then, as an author, you > > always need to check the API contract (so to wrap in try/catch) > > No, you don't. Nobody puts every method call in a try-catch today, just like > nobody is going to be checking for a rejection on a promise for this kind of > bug. > > As a developer you never need to look at what the API will do when you use > it incorrectly. You only need to use it correctly. > > When you use it incorrectly, you should get an exception, the same way as > when you call the wrong API. FWIW, based on personal experience with Promises, I think that the current design leads to sadness very quickly. No sooner than I wrote 30 lines of code using Promises, I was already stumped by an accidental typo (like this, for instance http://jsbin.com/cosic/1/edit?js,console). FWIW :)
Kind people pointed out to me that I have (unintentionally) non sequitur-ed the thread. My apologies :)
Just to close that non-sequiter, that is only a problem in Chrome due to poor dev tools; if you run that sample in Firefox it shows the error wonderfully. In other words this is not an inherent problem in promises, but in dev tools.
> In particular, if an API is called with bad arguments, then the exception should throw immediately, just like if it's name was typoed. The behaviour of: > > foo.bar(-1); // only accepts positive numbers > > ...should be the same as the behaviour of: > > foo.baz(1); // there's no "baz" method, it's called "bar" Is the rule applied recursively? For example, imagine we provide a Promise-returning function foo and it uses another function bar. function foo(x, y) { if (typeof x !== 'number) { throw TypeError('x must be a number'); } bar(-x, y); return new Promise(...); } Should we enclose bar with try-catch?
I'm only talking about APIs here. How people want this to work for their own code is up to them. The problem in comment 17 doesn't apply to APIs, because APIs are black boxes that by definition don't call other APIs incorrectly.
IMO I prefer the current spec - returning a rejected Promise rather than throwing an error. By the way, many APIs are currently using Promises. Chrome already returns a rejected Promise from APIs returning a Promise when there is an error, although the implementation is not perfect yet (see https://code.google.com/p/chromium/issues/detail?id=359386). Changing the draft will affect many APIs (we thought that part of them for example WebCrypto were stable and "shipped" them). It would be great if we conclude this discussion soon.
This inconsistency in rather horrible. var promise1 = object.foo(someParam); // you should not do anything interesting here, since foo() call may even got // wrong params. You're forced to wait for the promise to resolve/reject. Error reporting really should happen early in certain cases, like in param type validation.
(In reply to Olli Pettay from comment #20) > This inconsistency in rather horrible. And yet, we received exactly that complaint from Indexed DB users who ran into that issue with an API that works the way you propose (both synchronous and asynchronous failures): var request = store.put(userdata, key); request.onsuccess = ...; request.onerror = ...; "What do you mean I *also* need to catch exceptions from put() if the userdata is something that can't be cloned? I already have an error handler - make it consistent!"
I don't know of any APIs which have synchronous side-effects and which also return a promise. So you can't rely on anything having successfully happened on the line after the .foo() call anyway.
(In reply to Jonas Sicking from comment #22) > I don't know of any APIs which have synchronous side-effects and which also > return a promise. So you can't rely on anything having successfully happened > on the line after the .foo() call anyway. Such an API would be a terrible design anyway, and would hopefully be flagged as such on review.
(In reply to Jonas Sicking from comment #22) > I don't know of any APIs which have synchronous side-effects and which also > return a promise. So you can't rely on anything having successfully happened > on the line after the .foo() call anyway. Just to clarify, I think that should be rephrased as "observably synchronous side effects". For example, the enqueue and dequeue methods of http://wiki.ecmascript.org/doku.php?id=strawman:concurrency#infinite_queue have sync side effects, but not observably so.
(In reply to Jonas Sicking from comment #22) > I don't know of any APIs which have synchronous side-effects and which also > return a promise. So you can't rely on anything having successfully happened > on the line after the .foo() call anyway. Just passing wrong types of params to foo. It is not really a side-effect, but a basic type validation (or whatever term the spec uses). And I'm not interested in the success case, I'm interested in the case when an error has occurred. That information should be given to the caller asap.
(In reply to Olli Pettay from comment #25) > (In reply to Jonas Sicking from comment #22) > > I don't know of any APIs which have synchronous side-effects and which also > > return a promise. So you can't rely on anything having successfully happened > > on the line after the .foo() call anyway. > > Just passing wrong types of params to foo. It is not really a side-effect, > but a basic type validation (or whatever term the spec uses). > And I'm not interested in the success case, I'm interested in the case when > an error has occurred. That information should be given to the caller asap. Some "basic validations" can be performed synchronously, others can only be done async. Having to split error-handling logic between sync and async isn't great, as it produces the two-locations annoyance that has already been brought up as an author complaint, and is often quite arbitrary.
Marking WONTFIX per bug 25662 comment 4.