Open stefanpenner opened 10 years ago
I understand this is objective-c land, but as promises in JavaScript are now part of a formal standard, is there interest in aligning this project with those semantics?
RXPromise tries to follow the Promises/A+ spec. Especially, regarding your question, it tries to accomplish this:
"If x (the value which is used to revolve the promise) is a thenable, it attempts to make promise (the receiver) adopt the state of x, under the assumption that x behaves at least somewhat like a promise."
In other words, in RXPromise, if a pending promise B will be resolved with another promise A (either pending or resolved), the promise B "adopts the state" of promise A. More precisely, the set of continuations registered for A and B will execute on their respective execution context when the promise A gets resolved, and both promises now have the same state (either fulfilled
or rejected
- and in RXPromise possibly cancelled
).
Cancellation will be handled as well in a similar fashion. If promise B is already cancelled when the implementation adopts the state, promise A will be cancelled as well. Likewise, when the cancellation on promise B happens after the adoption, promise B also gets cancelled and vice versa.
If I take your example:
RXPromise* something = [[RXPromise alloc] initWithResult:original_error]; // state is rejected
RXPromise* promise = [[RXPromise alloc] init];
[promise resolveWithResult:something]; // note: will invoke synced_bind:
promise.then(nil, ^id(NSError* error) {
// Parameter `error` is the value of the assimilated promise `something`:
assert(original_error == error);
Then you have the following options:
// return a value (maybe be nil) in order to proceed normally:
return @"OK"; // or: return nil;
OR:
// "rethrow" the error:
return error;
OR:
// "throw" another error:
NSError* newError = [NSError errorWithDomain: ...];
return newError;
});
I believe my confusion/concern boils done to:
Everything else I have encountered so far, (as I get used to the Objective-c'isms) feels good, good job.
promise acting as a deferred (exposing its private resolve/reject)
Well, yes. Possibly, a solution would be to have a class RXDeferred
which subclasses RXPromise
. This would make the API more clean. However, since in this case a RXPromise
IS_A RXDeferred
actually, anyone can send "deferred" messages to a promise, and it would act accordingly as a deferred.
Another requirement is to let a resolver easily subclass a RXPromise (respectively a Deferred, it it were split). There are a few important use cases which require a subclass of RXPromise. So, splitting RXPromise into a Promise and a Deferred, should guarantee that this is still possible.
Not sure why I really chose this design, perhaps since Objective-C usually likes to use conventions over, well, compiler errors. So, here the convention for a consumer is, "don't resolve a promise" ;) A couple months ago, I implemented a C++ version of a promise where I chose a to make the deferred API private, very much as an effect of a knee-jerk reaction. So, I believe this design choice has something to do with the kind of language ;)
But seriously, I will investigate the options to make the deferred part a subclass and thus a "declared private" API for the consumer, unless you know a better solution. I would also prefer a "light weight" implementation relying on conventions over a "strict" implementation which could only be accomplished with a more complex class layout.
exposing both fulfill and resolve (I would like to suggest "resolve" be the only one of the two)
Strictly, if there is a resolveWithResult:
method, it makes the API fulfillWithValue:
and the API rejectWithReason:
redundant. Nonetheless, I need the implementations of both. IFF there will be a change in the Promise/Deferred API, I may consider to make the API of the deferred more concise and crisp as you suggested.
Everything else I have encountered so far, (as I get used to the Objective-c'isms) feels good, good job.
Thanks :)
[RXPromise promiseWithResult:anotherPromise]
should mimic the ES spec's Promise.resolve
It should accept a value, a promise, or an error. When accepting a promise as it's argument the newly constructed promise's fate should be that of the promise passed as the argument.
this is part of my first example:
var error = new Error();
var rejectedPromise = Promise.reject(error);
var promise = Promise.resolve(rejectedPromise)
promise.then(function(value) {
// never invoked
}, function(reason) {
reason === error // => true
});
Strictly, if there is a resolveWithResult: method, it makes the API fulfillWithValue: and the API rejectWithReason: redundant. Nonetheless, I need the implementations of both. IFF there will be a change in the Promise/Deferred API, I may consider to make the API of the deferred more concise and crisp as you suggested.
Ya it would appear that in objective-c land we need only the following:
[RXPromise resolveWithResult:...]
and [RXPromise promiseWithResult:...]
Where [RXPromise resolveWithResult:foo]
is basically just
if ([foo class] == self) {
return foo
} else {
return [self promiseWithResult:foo];
}
In theory though, wonder if we need both, resolveWithResult may be sufficient (in objective-c)
I understand this is objective-c land, but as promises in JavaScript are now part of a formal standard, is there interest in aligning this project with those semantics?
One very specific and confusing difference is how the promises compose.
in ES6: there is no concept of a promise fulfilling with another promise, when a promise is to resolve another promise, it merely assimilates the new promises state.
Simple example:
Why is this useful?
It encourages encapsulation, and mimics synchronous programmings try/catch
example continued:
sync:
async
To summarize, these chaining and assimilation semantics encourage best practices. Although this library is clearly not JavaScript, aligning only improves mindshare on the topic.