-
Notifications
You must be signed in to change notification settings - Fork 393
Description
As currently implemented, given the same theoretical set of Promises, a call to any() or some() will produce a different result depending on the timing of when those Promises are resolved or rejected.
For example:
If a Promise in the supplied set is resolved before any of the other Promises are rejected, any() will resolve the Promise it returned with the resolved value. If one of the Promises in the supplied set is subsequently rejected, it is ignored because the returned Promise has already been resolved.
If the timing is slightly different, and a Promise in the supplied set is rejected before any of the Promises are resolved, any() will reject the Promise it returned with the rejected value. If one of the Promsies in the supplied set is subsequently resolved, it is ignored because the returned Promise has already been rejected.
any() is documented as:
Return a promise that will resolve when any one of the supplied promisesOrValues has resolved.
and some() is documented as:
Return a promise that will resolve when howMany of the supplied promisesOrValues have resolved.
so this behavior would appear to contradict the documentation.
In their current form, this nondeterministic behavior sets any() and some() apart from their all(), map(), and reduce() brethren. Evaluating the same theoretical set of Promises, all(), map() and reduce() will always produce the same result, regardless of the timing of their resolution or rejection.
I noticed earlier this week that someone else reached out to @briancavalier on Twitter to demonstrate that passing an Array containing a rejected Promise and a resolved Promise to any() produces a Promise that rejects instead of resolving. This reveals that any() and some() in their current form can also potentially give greater weight to Promises that appear earlier in the supplied Array.
I believe that the correct behavior would be for any() and some() to defer rejection until after all potential resolutions have been ruled out. So, any() would resolve as soon as a Promise resolves but wouldn't reject until all of the supplied Promises have been rejected (and there is no possibility of a Promise resolving). Similarly, some() would resolve as soon as the specified number of Promises resolve, and would only reject once there was an insufficient number of pending Promises available to meet that threshold.
This also raises the question of what rejection value should be returned. Currently, the first rejection value is returned. This also seems odd (and the same applies to all(), map(), and reduce()).
I'm starting to think that all of these utility methods should return consistent rejection values that are oriented relative to the attempt to perform that operation.
If a developer wants to be aware of the reason why a specific Promise in the supplied set was rejected or wants to add recovery logic, that should be applied to the originating Promise (and that recoverable Promise should be supplied in the set instead) rather than to the aggregate utility operation.
If they are adding fault handling or recovery logic to the Promise returned by the aggregate utility method, that should be considered as being relative to the failure of that method's operation, not the individual elements originally supplied to that method.
Thoughts?