-
Notifications
You must be signed in to change notification settings - Fork 4
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Strawman: Promise Creation API/D #18
Comments
This precludes implementations from extending behavior, e.g. by accepting an options object as the second argument.
What is your reason for this? Take the following example: function foo(){
var bar = "baz";
var promise = new Promise(function(resolve){ resolve(bar); });
bar = "thud";
return promise;
} The result would be function foo(){
var bar = "baz";
var dfd = defer();
dfd.resolve(bar);
bar = "thud";
return dfd.promise;
} |
I don't like the fact that you can't build defer out of this spec (without creating your own proxy to the resolver which waits for the resolver to be available, and that's kind of insane). Requiring |
Maybe hair splitting, but technically, it doesn't. There can be additional args, they just can't be in the signature. I believe ES5 has used this in many places for optional args, e.g.
I'd like to understand the reasoning behind this one as well, especially given the observations of @novemberborn and @ForbesLindesay. I assume we'd also be ignoring any return value from |
Ha! At that point though, why bother specifying |
@novemberborn like I said, I was splitting hairs :) I'd also like to understand @domenic's reasons for specifically requiring length 1. |
I'm not married to this, but, just like in the ES5 specs, this would signal that any other params have default values. (Indeed, in ES6, a function's
This is crucial if we want the thenable assimilation equivalence. Thenables are not to be trusted; Furthermore, I think it actually works out nicely. It solves the problem of what to do if |
I just noticed my original assimilation equivalence was broken, since I used new Promise(function (resolver) { thenable.then(resolver, resolver.reject); }) I'm really torn between |
the assimilation as construction could really alleviate this problem. |
Updated to switch back to |
Just noticed that Promise.from 3 implies that thenable.then should be called as a function (without its original this). That may not be valid for some thenables. In fact, since we didn't make that a requirement of Promises/A+, a promise might fail there too. I think we probably just need to tweak the wording/example. |
Good call, @briancavalier. We probably should just rephrase that to use more general prose. I also don't want to mandate how implementations implement the assimilation; I tried to kind of get around that by saying "the result of", but it could be done better. |
Sure, but the library could ensure
Does it? This draft doesn't mention any behavior, is it supposed to be an uncaught exception? At least if we throw synchronously I'm likely to be constructing a promise from within another promise callback so my program won't crash. Regarding In the following example, having a separate factory method is cumbersome compared to having a User.prototype.getEmail = function(){
return new Promise(function(resolve))
db.users.findOne({ _id: this.id }, { email: true }, function(err, doc){
if(err){
resolve.reject(err);
}else{
resolve(doc.email);
}
});
});
}; Note that the factory needs to access |
The issue with then will be fixed by ES6 arrow functions so I don't think we should worry about it at all. |
I think this is pretty good, but we have some remaining issues to flesh out as a group. I'll post new issues with them before we can converge on something more final. |
Some questions in trying to implement this:
Can Is the following an appropriate algorithm for adopting the state of the thenable? function adoptState(thenable, resolve, reject){
try{
thenable.then(resolve, reject);
}catch(error){
reject(error);
}
} Note that if the thenable fulfills with another thenable, |
@novemberborn due to promises-aplus/promises-tests#20 I realized that "adopt the state" is not very well specified, and outlined the problem in promises-aplus/promises-spec#75. My solution is to specify "thenable assimilation" much more in-depth. The algorithm is exactly like yours; you can see it in prose over in at promises-aplus/promises-spec#76. In particular, the recursive-adoption strategy is present. |
I've implemented this draft in https://github.com/novemberborn/legendary/blob/3f96b54de4e76dabe8658980cc54ba502b22c339/lib/Promise.js, using same-turn/crash invocation of the factory method. |
Renamed "factory" to "resolver" as per @wycats. It's so obvious in hindsight :O. |
I really dislike "The Promise Constructor - 5":
Since my primary export for my library is the promise constructor, I'd really like to have it act as a clever polymorphic function capable of handling assimilation and converting a value into a promise for a value. Failing this I'll have to have an adapter that looks like: var Promise = require('promise');
function Adapter(fn) {
if (typeof fn !== 'funciton') throw new TypeError('fn is not a function');
return Promise.apply(this, arguments);
}
Adapter.prototype = Object.create(Promise.prototype);
Adapter.prototype.constructor = Adapter;
module.exports = Adapter; Which feels like cheating. |
This seems like a very bad idea. In particular, making |
I've just partially broken anyone who doesn't use semver properly to lock down their version numbers for promise by updating it to this API. It should still run but it prints nasty deprecation warnings. I've also written a sketchy unit test for this API at then/promise/test/resolver-tests.js. There are loads of corner cases it doesn't test, but it at least aims to test the things most likely to fail. The only thing I know isn't inline with this API is that I currently support returning a |
OK, you were write about not using Is there any reason why we can't support Point 5 becomes 2 points:
That doesn't seem like too much overhead to put on implementers and it would be a hugely useful feature. |
My personal preference is for functions/methods to do one thing.
What advantages do you see over having 2 separate APIs, one for construction and one for assimilation? It may be a point of confusion for implementors and users if we end up specifying a sync resolver callback, but async assimilation inside the same constructor. |
The downside is verbosity: var promise = require('promise');
var p = promise(thenable);
var p2 = promise((resolve) => resolve(10)); vs. var promise = require('promise');
var p = promise.assimilate(thenable);
var p2 = promise((resolve) => resolve(10)); I prefer the former, and I think assimilating a thenable is a very common thing to want to do. |
I too think functions that do only one thing is a good idea. (Also, it helps optimizing compilers.) @ForbesLindesay, I think you have trapped yourself in a corner by making your library's primary export be the promise constructor, and now you are trying to influence the promise constructor API to make it work better as your library's primary export. |
I mostly think the primary output of a library should be the constructor, much like the primary output of an Thinking about the eventual road to standardization and inclusion in a core JS spec (which is what we eventually want), the core thing we'd expect to standardize would be the |
I've implemented the latest version of this draft in legendary@0.8.0 and it works quite nicely. What are our next steps here? |
Someone needs to add this document as the readme (marked as a working draft), then we need to begin getting it ratified, just like we're currently doing the next versions of Promises/A+ |
when.js 2.0 also includes this API, although it is not public, and only used internally. I am planning to expose it in an upcoming release. I agree it would be a good forcing function for us to move this to the README. How do others feel about that? |
RSVP v2 also has this. I think this is close to complete, but wanted to get 1.1 of the main spec all done before spending much more time on it. |
@domenic I agree about getting promises-spec 1.1 done first. |
Latest TC39 thinking is that new built-ins should not have [[Call]] do the same as [[Construct]]. For example, this won't be the case for Thus, I think we should drop the "can be called without |
Thanks for the pointer @domenic. Yeah, if that's the ES6 direction, I agree we should head that way as well and remove "can be called without I can't really see a good answer to the strict vs. allowing subclasses issue-- |
I think it's pretty final; the new semantics are widely agreed upon, although this particular consequence of it is just starting to come out into the open. See also slightlyoff/Promises#71. One thing, perhaps, would be disallow special-case code like |
Preventing special case code would certainly be helpful for parasitic inheritance. |
The difference in Guzzle promise implementations mainly lay in the construction. According to promises-aplus/constructor-spec#18 it's not valid. The constructor starts the fate of the promise. Which has to been delayed, under Guzzle. The wait method is necessary in certain callable functions situations. The constructor will fail it's execution when trying to access member method that's null. It will happen when passing an promise object not fully created, itself. Normally an promise is attached to an external running event loop, no need to start the process. The wait function/method both starts and stops it, internally.
…g, mark as skiped for now. The difference in Guzzle promise implementations mainly lay in the construction. According to promises-aplus/constructor-spec#18 it's not valid. The constructor starts the fate of the promise. Which has to been delayed, under Guzzle. The wait method and constructor setup in Guzzle is necessary in certain callable functions situations. The constructor will fail it's execution when trying to access member method that's null. It will happen when passing an promise object not fully created, itself. The cause for some failing tests. Normally an promise is attached to an external running event loop, no need to start the process. The wait function/method both starts and stops it, internally.
Terminology
Some additional terms are introduced for talking about promises:
Promise Fates
A promise's fate describes what can happen to the promise in the future. It is a distinct concept from its state, although they are related.
A promise can have one of two fates:
Note that a promise can only move from unresolved to resolved, never the other way around.
The Promise Constructor
An implementation of this specification supplies a function responsible for constructing new promises. This promise constructor takes as its sole argument a user-supplied resolver function which is given the means to resolve the constructed promise.
Here we refer to this promise constructor by the name
Promise
for concreteness, although the name is implementation-specific.new
, with the same results.Object.getPrototypeOf(promise) === Promise.prototype
must be true.promise.constructor === Promise.prototype.constructor === Promise
must be true.Promise.length === 1
must be true.resolver
is not a function, the implementation must throw aTypeError
.resolver
is a function,Promise
returns.The Resolver Arguments
When
resolver
is called, it is given the constructed promise's resolver arguments, which have the ability to resolve the promise.Calling
resolve(x)
promise
is resolved, nothing happens (in particular, no exception may be thrown).[[Resolve]](promise, x)
.Calling
reject(reason)
Calling
reject(reason)
resolves the promise to a rejection reason (i.e. rejects the promise).promise
is resolved, nothing happens (in particular, no exception may be thrown).promise
withreason
as its rejection reason.Resolver Throwing
If
resolver
throws an exception, call ite
,promise
is resolved, nothing happens (in particular, no exception may be thrown).promise
withe
as its rejection reason.The text was updated successfully, but these errors were encountered: