8000 Interest in aligning with ES6 Promise semantics? · Issue #21 · couchdeveloper/RXPromise · GitHub
[go: up one dir, main page]
More Web Proxy on the site http://driver.im/
Skip to content

Interest in aligning with ES6 Promise semantics? 8000 #21

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

Open
stefanpenner opened this issue May 13, 2014 · 5 comments
Open

Interest in aligning with ES6 Promise semantics? #21

stefanpenner opened this issue May 13, 2014 · 5 comments
Labels

Comments

@stefanpenner
Copy link

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:

var rejectedPromise = Promise.reject(new Error);
var promise = Promise.resolve(rejectedPromise)

promise.then(function(){
  // will not invoke
}).catch(reason) {
  // will invoke, with the reason of the assimilated promise
  // to propagate the rejection
  throw reason;

  // return to "catch" and handle the rejection, providing a new success value"
  return 1;

  // return a promise, to "retry"
  return User.find(1);
}); 

Why is this useful?

It encourages encapsulation, and mimics synchronous programmings try/catch

example continued:

sync:

function fetch() {
  var something = ajax('/something');
  var somethingElse = ajax('/something-else/' + something.id);
  // if either attempt throws, fetch throws
  return something;
}

async

function fetch() {
  return ajax('/something').then(function(value) {
    return ajax('/something-else/' + value.id);
  });
}
// if either attempt rejects, the promise returned from fetch rejects

// refactor further
function fetchSomethingElse(value) {
  return ajax('/something-else/' + value.id);
}

function fetch() {
  return ajax('/something').then(fetchSomthingElse);
}
// identical semantics.

To summarize, these chaining and assimilation semantics encourage best practices. Although this library is clearly not JavaScript, aligning only improves mindshare on the topic.

@couchdeveloper
Copy link
Owner

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;
});

@stefanpenner
Copy link
Author

I believe my confusion/concern boils done to:

- (void) resolveWithResult:(id)result {
dispatch_barrier_async(Shared.sync_queue, ^{
[self synced_resolveWithResult:result];
});
}
- (void) fulfillWithValue:(id)value {
assert(![value isKindOfClass:[NSError class]]);
if (dispatch_get_specific(rxpromise::shared::QueueID) == rxpromise::shared::sync_queue_id) {
[self synced_fulfillWithValue:value];
}
else {
dispatch_barrier_async(Shared.sync_queue, ^{
[self synced_fulfillWithValue:value];
});
}
}

  1. promise acting as a deferred (exposing its private resolve/reject)
  2. exposing both fulfill and resolve (I would like to suggest "resolve" be the only one of the two)

Everything else I have encountered so far, (as I get used to the Objective-c'isms) feels good, good job.

@couchdeveloper
Copy link
Owner

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 :)

@stefanpenner
Copy link
Author

[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.

assert(![result isKindOfClass:[RXPromise class]]);

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
});

@stefanpenner
Copy link
Author

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)

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
Projects
None yet
Development

No branches or pull requests

2 participants
0