This repository was archived by the owner on Oct 7, 2020. It is now read-only.
-
-
Notifications
You must be signed in to change notification settings - Fork 19
user-expectations-6 #21
Merged
Merged
Changes from all commits
Commits
Show all changes
2 commits
Select commit
Hold shift + click to select a range
File filter
Filter by extension
Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
There are no files selected for viewing
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,76 @@ | ||
### Use Case #6 | ||
|
||
Name: Synchronous exception in Promise constructor always rejects even if promise is already resolved. | ||
|
||
Brandy is writing code with Promises. She writes the following program, and is surprised/confused by not seeing the `"resolved!"` message in the console. That confusion causes her to worry that the Promise constructor is not even running, even though it seems like `showSuccess()` is being called. | ||
|
||
```js | ||
var OK = true; | ||
|
||
var p = new Promise(function c(resolve,reject){ | ||
if (OK) { | ||
resolve("Good to go!"); | ||
cosnole.log("resolved!"); // console misspelled here | ||
} | ||
else { | ||
reject("Oops!"); | ||
console.log("rejected."); | ||
} | ||
}); | ||
|
||
p.then( showSuccess, showError ); | ||
// ..nothing printed to console :( | ||
``` | ||
|
||
### What happens | ||
|
||
The promise is already fulfilled (with `resolve(..)`), so the exception that then happens from `cosnole.log(..)` being misspelled is silently swallowed. | ||
|
||
### Why it happens | ||
|
||
Promises can only be resolved to a fulfillment or rejection, and once resolved, cannot be changed. This normally makes sense, but in this specific case of an accidental exception that occurs synchronously subsequent to the otherwise resolution (fulfillment or rejection), the user never expects the promise machinery to just suppress/hide that exception. | ||
|
||
### What can we maybe do better | ||
|
||
This could be seen as a case for a warning. But that warning would likely be confusing to track down, because it's likely going to talk about the promise trying to be rejected after it's already resolved. This code doesn't look like it matches that pattern, since there's a clear `if-else` that either fulfills or rejects. It may be confusing for the user to connect-the-dots that the excess rejection is actually an unexpected JS exception. | ||
|
||
In this case, a synchronous exception is different from if the user had mistakenly called `reject(..)`. It's not a mistake of the usage of the API, but (arguably) a more serious exception of writing broken JS. Being more serious, it calls for this case to be handled differently from handling for simply using the Promise mechanism incorrectly. | ||
|
||
By virtue of how the promise is constructed (with a synchronous resolution and `if..else`), the user is expecting either the fulfillment or rejection paths to cover all possible outcomes. The user is most likely any exception to result in the promise having been rejected, with the exception as its reason. As such, the most appropriate place to report the exception is in the promise rejection path. Thus, the synchronous exception needs to override the prior synchronous resolution. | ||
|
||
Because the suggested change would be breaking backwards compatibility with current promises, the user should be able to opt-in to the different behavior **prior to the construction**. For example, they could set a flag to turn on a sort of "strict mode" for the Promise construction. | ||
|
||
---- | ||
|
||
Additional Notes: | ||
|
||
1. The suggested behavior for the constructor is implemented in this [`BetterPromise` experiment](https://gist.github.com/getify/1173cac45d15fc4ff0a880f32fd598ab). | ||
|
||
2. This notion of suggested overriding is not without precedent in JS, btw. The `finally {..}` clause in a function is notably able to override a previous synchronous `return` value from that same function. | ||
|
||
In fact, one could think of this suggested behavior kind of like sugaring for this: | ||
|
||
```js | ||
var p = new Promise(function c(resolve,reject){ | ||
var _fulfillment; | ||
var _rejection; | ||
|
||
try { | ||
if (OK) { | ||
_fulfillment = "Good to go!"; | ||
cosnole.log("resolved!"); // console misspelled here | ||
} | ||
else { | ||
_rejection = "Oops!"; | ||
console.log("rejected."); | ||
} | ||
} | ||
catch (err) { | ||
_rejection = err; | ||
} | ||
finally { | ||
if (_rejection) reject(_rejection); | ||
else if (_fulfillment) resolve(_fulfillment); | ||
} | ||
}); | ||
``` |
Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I think it would be interesting to also suggest an
asnyc_hook
here - a function that will be called on allthrow
s inside a promise constructor and report them to APMs or implement the behavior below, WDYT?There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I may not fully know what an "async_hook" is, but I shyed away from that because this use-case is specifically about synchronous behavior (both the resolution and the exception overriding it).
Having a hook to be notified of these cases is not a bad idea, but I organized this use-case under "expectations" because I think most users would tend to just expect this suggested behavior. So a hook would sorta be augmentative of that rather than an alternative.