I remember being supremely annoyed by this as well. You have to write all this extra code, just to get a simple deferred object - which I totally thought I needed all the time. However, as I've worked with promises to develop my own software and libraries, being (forcibly) encouraged to avoid deferreds has significantly improved the quality and elegance of my async code.
First, a small, practical benefit. Using the constructor is threadsafe, whereas deferreds are not:
$modal.open = function(...) {
return new Promise(resolve, reject) {
...
// exception thrown during construction is always caught && rejected
...
resolve($modal) ;
}
$modal.open = function(...) {
var deferred = Promise.defer();
...
// exception thrown during construction is not caught; outer code needs to try/catch
...
deferred.resolve();
In the world of promises, you want to avoid having any synchronous code that isn't wrapped with a promise function. This helps to ensure that all errors are caught and thus never break the process. Deferreds encourage a development pattern that circumvents this process, whereas the constructor encourages a pattern that fosters it. As a result, deprecating defer() forces a developer to use threadsafe code, or think before s/he tries to manually create a deferred, to determine if it is really needed or not.
This leads us into the real issue -- developers' use of the library. Most of the times when I thought I needed a deferred, there was actually a better way to design my library/code. Not always is this the case, but I would say it happened more often than not. In particular, I was used to thinking in terms of callbacks, so I was always trying to get my hands on a callback -- generally just to give it to another callback-based library. Bluebird of course provides generic tools to help interface with callback libraries, such as promisify etc.
Promises require a paradigm shift in how you think about and design asynchronous code. One of the policies that came out of my paradigm shift is that I almost always now return a promise from my functions, with increasingly few exceptions.
For example, you linked some code that opens a dialog box. The modal contains a promise that represents a user response (probably true for "ok", false for "cancel", or something like that). My expectation of a modal dialog library would be that it should return a promise. At the very least, this promise would fulfill when the dialog successfully opens (possibly to some widget controlling the modal), or preferably just to the user's choice. This guarantees a consistent interface (a consuming developer can always rely on a function returning a promise), and it guarantees thread-safety. The first example above represents this approach.
Long story short, deferreds work against the Promise paradigm by fostering reliance on callbacks outside the protection of a threadsafe function call. The constructor pattern works for the Promise paradigm by making callbacks threadsafe, and discouraging their overall use, thereby encouraging the exploration of simpler and/or more "correct" solutions.
Yeah, this is an interesting issue. Like any tool or structure in code, promises had an initial strategy of implementation, but as they've been studied in real-world environments, our understanding of how best to design/use them has changed (look for example at the differences between Promise A vs Promise A+ specifications).
The problem with .done() is kind of similar to the problem of SQL injection. SQL libraries in any language will generally provide a way to escape/sanitize values so that they can be sent to the database securely, without corrupting the SQL language that tells the database what to do with the information. The trouble is, if a developer forgets to sanitize just one value, that opens up a hole in his security that can potentially destroy his entire database.
.done() is similar. It is designed to crash the current process if an error is encountered that cannot be handled by the system (a lot like an unhandled exception). One objection to this would be that well-designed code should never hard-fail; it should always crash in a way that e.g. relays crash information to the appropriate parties in a reasonably professional format.
However, even if we're cool with hard-crashes in production code, .done() presents a bigger problem. If a developer forgets to use it in just one place, an error that should crash the process will silently fail. Errors will be happening, but the code will continue on as though nothing is wrong, which can lead to other bugs/errors/problems. The root of these problems can very difficult to trace; for example, if data is being pushed into a queue, and popped out when a promise fulfills, an unhandled error can cause the queue to consume all available memory, crashing the process with little clue as to the reason (just that we have a memory leak).
So even though .done() sounds like a good idea, as we've watched promises operate in the real world, it is one of those design choices which many have come to believe is flawed. Because Bluebird is designed to be a drop-in replacement for default browser library, things like .done() and .defer() have to be supported. However there are better ways to handle their use cases, and they are therefore discouraged in new code.
In the place of .done(), Bluebird checks for unhandled rejections at the (beginning?) of each tick, which usually (not always) means that the rejection will never be handled (handlers are generally attached before promise resolution, but not always). By default it then logs a message in the console, so that no unresolved error will go unreported. This can be overridden with global handlers that define other functionality (like process crashing, if that's really desired). As a result, even if a developer forgets to use .done(), the error will no longer be swallowed silently.
I hope that helps to answer your questions; they're both ones that I've had in the past.
God bless,
~ Nate