cujojs / when

A solid, fast Promises/A+ and when() implementation, plus other async goodies.
Other
3.44k stars 396 forks source link

Fixed `createCallback` example #437

Closed mweststrate closed 9 years ago

mweststrate commented 9 years ago

According to the typings at https://github.com/borisyankov/DefinitelyTyped/blob/master/when/when.d.ts a deferred itself is a resolver which can be passed direclty to the createCallback method.

briancavalier commented 9 years ago

Hey @mweststrate, both are valid. One reason to prefer passing deferred.resolver is slightly stricter separation of concerns. That is, deferred.resolver is only the resolver, whereas passing the whole deferred object directly will grant the receiving party access to the promise as well (which lives at deferred.promise).

In the particular case of createCallback it doesn't really matter in practice. However, I think it's better to show passing deferred.resolver in examples. Does that make sense?

mweststrate commented 9 years ago

(posted comment another time to enable markdown)

Hi Brian,

Yes, that makes sense, thanks for the explanation.

Maybe a related question, I observed that joining (when.join) on a bunch of deferreds does not seem to propagate (errors) correctly, while joining on their promises does (e.q. when.join(defA, defB) versus when.join(defA.promise, defB.promise). I guess that is technically correct but also a bit verbose. Wouldn't it be convenient in general to use automatically use the promise of a deferred everywhere where a deferred is passed into the api but a promise is excpected? (conceptually, to me a deferred is a special kind of a promise instead of something that has a promise)

briancavalier commented 9 years ago

@mweststrate Good questions. I'll try to provide some info.

(conceptually, to me a deferred is a special kind of a promise instead of something that has a promise)

The primary concept in when.js is the promise. Conceptually a promise,resolver entangled pair represents a one-way, secure communication channel from a producer to one or more consumers. A deferred object is something of synthetic thing, and its only real purpose to hold a (promise,resolver) pair. The Promise constructor more clearly separates the promise and the resolver, and that's why it is the primary API for creating promises in ES6.

With that in mind, there are a few reasons we "encourage" (read: force) people to use promises directly:

  1. Giving out the deferred grants too much authority by default: the recipient has the power to both control the promise's state, via the resolver, and observe its outcome, via the promise. The typical and intended usage pattern of promises is for a producer to retain the resolver and to give out the promise. So, we want to help people develop that good habit.
  2. Interoperability among promise implementations is important. There's no standard for deferred objects. People may try to pass a deferred object from another lib into when.join, and expect it to just work. Since there's no standard, that foreign deferred could have an arbitrary format, for example it may define a .promise() method to retrieve the promise instead of a .promise property. It would be extremely confusing for developers if some deferred objects just worked, and others didn't.
  3. Doing duck type checks in every API method (if we did it for when.join, people would expect it everywhere), would be an unnecessary performance hit.

I hope that helps.

briancavalier commented 9 years ago

@mweststrate Closing this, but please do feel free to continue to discuss here, or reopen if you feel that there is still something that requires a change.

mweststrate commented 9 years ago

@briancavalier Thanks for the extensive explanation, i'll guess I have to just work carefully with deferreds. I see that there is a bucket of 'promises' and 'non-promises', and that the line has to be drawn somewhere :).