This repository has been 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
Solutions Exploration #23
Closed
Closed
Changes from all commits
Commits
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 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,118 @@ | ||
# Solutions Exploration | ||
The purpose of this document is to explore possible hooks and/or APIs that may be used to resolve at least some of the use-cases presented in the "extras" section of this repo. | ||
|
||
Suggestions will be listed and discussed in an arbitrary order and may contain overlaps in functionality as well as in the problems they intend to solve. | ||
|
||
## Suggestion I - Async Function Hooks | ||
**Idea:** Provide hooks for prominent moments in an async function's lifecycle and allow values to be replaced by the hooks. | ||
|
||
## Example | ||
See these [TypeScript hook definitions](https://github.com/itaysabato/regrettable/blob/master/hooks/hook-types.ts). | ||
|
||
More info [here](https://github.com/itaysabato/regrettable#api-by-example). | ||
|
||
## Suggestion II - Async Functions Controller | ||
**Idea:** Give the invoker of an async function (some) ability to control its execution in a backward compatible way. | ||
|
||
### Example | ||
The following code should print out `Waited 50 ms`: | ||
```js | ||
// Does not exist: | ||
const {getController} = require('async_hooks'); | ||
|
||
const setTimeoutPromise = require('util').promisify(setTimeout); | ||
|
||
async function wait(ms) { | ||
const before = Date.now(); | ||
|
||
try { | ||
await setTimeoutPromise(ms); | ||
} | ||
finally { | ||
console.log("Waited", Date.now() - before, 'ms'); | ||
} | ||
} | ||
|
||
let p = wait(100); | ||
const controller = getController(p); | ||
|
||
setTimeoutPromise(50) | ||
.then(() => controller.return()); | ||
``` | ||
Since the `getController` API only works on promises returned from async function invocations, the invoker can have full control over who gets to abort the operation. | ||
This can easily be done in a generic way: | ||
```js | ||
// Naively assume it is indeed an async function: | ||
function enhance(someAsyncFunction) { | ||
return (...args) => { | ||
const p = someAsyncFunction(...args); | ||
const controller = getController(p); | ||
|
||
return { | ||
// If aborted, promise will be fulfilled with an undefined value - this can be modified in various ways: | ||
promise: Promise.resolve(p), | ||
abort: () => controller.return(), | ||
}; | ||
} | ||
} | ||
|
||
const { | ||
// Can be safely passed to anyone: | ||
promise, | ||
|
||
// Only the invoker is able to abort: | ||
abort, | ||
} = enhance(wait)(1000); | ||
|
||
``` | ||
|
||
## Suggestion III - Promise Interceptors | ||
**Idea:** Provide new promise hooks that not only _inform_ of promise creations and resolutions but can also _affect_ the outcome. | ||
|
||
[This reference implementation](https://gist.github.com/itaysabato/f78394793ae265c7895e862c2b2bd215) demonstrates a possible implementation of a "Bluebird-style" cancellation API on top of native promises via a promise _interception_ hooks API that I drafted therein. | ||
|
||
### Problems | ||
The interception API and implementation given above has a few addressable shortcomings: | ||
|
||
1. Cancellation does not work in conjunction with async functions / generators. | ||
This is because it only keeps track of direct parent-child promise relationships. | ||
For the same reason, cancellations do not propagate to "followed" promises, e.g. | ||
```js | ||
const followed = Promise.resolve(); | ||
const parent = Promise.resolve(); | ||
const child = parent.then(() => followed); | ||
``` | ||
This can be solved by adding an additional hook such as `interceptFollow(promise, followed)` to keep track of these cases as well. | ||
|
||
2. Cancel-aware `finally` must be explicit. This is because the hooks do not provide knowledge of the _kind_ of relationship between a parent and its child nor the type of resolution. | ||
If we add this information to the promise init hook (e.g. `interceptPromiseInit(promise, parent, kind, next)` where `kind` could be `onFulfilled`, `onRejected`, `onFinally`, etc.) | ||
then we could manage "native" `finally`s correctly. | ||
|
||
3. Even if the above two issues are resolved as suggested, "static" finally statements in async functions or generators are not guaranteed to be tracked (depending on internal implementation). | ||
E.g. the following code may or may not print something: | ||
```js | ||
const setTimeoutPromise = require('util').promisify(setTimeout); | ||
|
||
async function finallySay(something) { | ||
try { | ||
await setTimeoutPromise(0); | ||
} | ||
finally { | ||
console.log(something); | ||
} | ||
} | ||
|
||
cancel(finallySay("Cancelled")) | ||
``` | ||
|
||
## Suggestion IV - Informative Promise Hooks + Async Functions Controller | ||
**Idea:** Combine the above two approaches such that async functions and generators are cancelled via `.return()` and relationships between promises are tracked via promise hooks. | ||
|
||
### Details | ||
**_TBD_** | ||
|
||
## Suggestion V - Benji's Context Idea | ||
**Idea:** **_TBD_** | ||
|
||
### Details | ||
**_TBD_** |
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.
How do you differentiate between Promises created by async functions and ones created with the new Promise constructor? Do you have an internal flag or something?
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.
It would really be up to the implementer, but I imagine you can internally map promises created for async functions to their controllers. Other kinds of promises would simply have no mapping (and no controller).