-
Notifications
You must be signed in to change notification settings - Fork 299
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
Define a 'CancelationController' and 'CancelationSignal' interface. #434
Changes from 2 commits
3f57df3
61c6e72
9282bf3
8681c81
edce169
f64a92f
36028dd
b744aa7
28b01bc
4273de9
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -1419,6 +1419,98 @@ that gave folks all the wrong ideas. <a>Events</a> do not represent or cause act | |
can only be used to influence an ongoing one. | ||
|
||
|
||
<h3 id=controlling-promises>Controlling {{Promise}}s</h3> | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. This actually isn't really specific to promises. For example it could be used to cancel a stream: const readableStream = getStream({ signal });
// ... later ...
signal.abort(); // basically the same as readableStream.cancel() There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Though, it's not clear to me why you'd use this model for streams when they have a built in cancellation mechanism. Do you think it's worth changing the streams API in line with what we have to do here for Promises? There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. The idea would be to allow people to pass in the signal to many APIs, streams being just one. So it's OK if an API has both a dedicated cancel mechanism and also accepts the platform-generic one, IMO. |
||
|
||
<p>Though {{Promise}} objects don't have any built-in cancellation mechanism, many | ||
{{Promise}}-vending APIs require cancellation semantics. {{PromiseController}} is meant to support | ||
these requirements by providing an {{PromiseController/abort()}} method that toggles the state of | ||
a corresponding {{PromiseSignal}} object. The API which wishes to support cancelation can accept | ||
such a {{PromiseSignal}}, and use its state to determine how (not) to proceed. | ||
|
||
<div class=note> | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
|
||
For example, a hypothetical <code>DoAmazingness({ ... })</code> method could accept a | ||
{{PromiseSignal}} object in order to support cancellation as follows: | ||
|
||
<pre class=lang-javascript> | ||
const controller = new PromiseController(); | ||
const signal = controller.signal; | ||
DoAmazingness({ ..., signal }).then(result => ...); | ||
|
||
... | ||
|
||
controller.abort(); | ||
</pre> | ||
|
||
APIs that require more granular control could extend both {{PromiseController}} and | ||
{{PromiseSignal}} according to their needs. | ||
</div> | ||
|
||
<h4 id=interface-promisecontroller>Interface {{PromiseController}}</h3> | ||
|
||
<pre class="idl"> | ||
[Constructor(), Exposed=(Window,Worker)] | ||
interface PromiseController { | ||
readonly attribute PromiseSignal signal; | ||
|
||
void abort(); | ||
}; | ||
</pre> | ||
<dl class=domintro> | ||
<dt><code><var>controller</var> = new <a constructor lt="PromiseController()">PromiseController</a>()</code> | ||
<dd>Returns a new <var>controller</var> whose {{PromiseController/signal}} attribute value is set | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I'd remove "attribute value" (in general mentioning "attributes" in dev-facing docs is just confusing). There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Sure. |
||
to a newly created {{PromiseSignal}} object. | ||
|
||
<dt><code><var>controller</var> . </code>{{PromiseController/signal}} | ||
<dd>Returns the {{PromiseSignal}} object associated with this object. | ||
|
||
<dt><code><var>controller</var> . </code>{{PromiseController/abort()}} | ||
<dd>Invoking this method will set this object's {{PromiseSignal}}'s [=PromiseSignal/aborted flag=], | ||
thereby signaling to any observers that the associated activity should be aborted. | ||
</dl> | ||
|
||
The <dfn attribute for=PromiseController>signal</dfn> attribute must return the value to which it | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Needs |
||
was initialized. When a {{PromiseController}} is created, the attribute must be initialized to a | ||
newly created {{PromiseSignal}} object. | ||
|
||
The <dfn method for=PromiseController><code>abort()</code></dfn>, when invoked, must run these | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Missing "method" |
||
steps: | ||
|
||
<ol> | ||
<li>Let <var>signal</var> be this object's {{PromiseController/signal}}. | ||
<li>Set <var>signal</var>'s [=PromiseSignal/aborted flag=]. | ||
<li>[=Fire an event=] named <code>abort</code> at <var>signal</var>. | ||
<li>Run <var>signal</var>'s [=PromiseSignal/abort steps=]. | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I suspect we probably want the event to dispatch after the abort steps have run as the event listeners can cause side effects the abort steps might not anticipate. There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Inverted in 8681c81. |
||
</ol> | ||
|
||
<h4 id=interface-cancellationcontroller>Interface {{PromiseSignal}}</h4> | ||
|
||
<pre class="idl"> | ||
[Exposed=(Window,Worker)] | ||
interface PromiseSignal : EventTarget { | ||
readonly attribute boolean aborted; | ||
|
||
attribute EventHandler onabort; | ||
}; | ||
</pre> | ||
<dl class=domintro> | ||
<dt><code><var>signal</var> . </code>{{PromiseSignal/aborted}} | ||
<dd>Returns true if this {{PromiseSignal}} has been aborted, and false otherwise. | ||
</dl> | ||
|
||
Each {{PromiseSignal}} has an <dfn for=PromiseSignal>aborted flag</dfn> which is unset unless | ||
otherwise specified. | ||
|
||
Each {{PromiseSignal}} has an <dfn for=PromiseSignal>abort steps</dfn> algorithm which is | ||
executed when its [=PromiseSignal/aborted flag=] is set. Unless otherwise specified, this | ||
algorithm is a no-op. | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Hmm, so how would this be used? It may need an example. Is the idea that e.g. fetch() would accept a There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I was thinking that Fetch would subclass these and define custom steps there. Having the steps be part of the base class helps in that you don't have to mutate most other things. How Fetch handles multiple fetches and such could be done in such a setup with a single set of abort steps. Now, I don't know for sure that Fetch would subclass these. @jakearchibald should probably weigh in. There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Hmm, I see. Fetch would subclass, but streams (and possibly whatever @mikewest wants this for originally) would not. There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. But maybe you're right that we should design this more so that it even works for all scenarios when not subclassed. I do think that whatever this get passed to needs to be able to hook into it, since we don't really have specification-only event listeners (I think that would be a bigger thing to add). |
||
|
||
<p class="note no-backref">The [=PromiseSignal/abort steps=] algorithm enables APIs with more | ||
complex requirements to react in a reasonable way to {{PromiseController/abort()}}. For example, | ||
a given API's [=PromiseSignal/aborted flag=] may need to be propagated to a cross-thread | ||
environment (like a Service Worker). | ||
|
||
The <dfn attribute for=PromiseSignal>aborted</dfn> attribute's getter must return true if the | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Needs |
||
object's [=PromiseSignal/aborted flag=] is set, and false otherwise. | ||
|
||
<h2 id=nodes>Nodes</h2> | ||
|
||
|
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.
This probably doesn't belong under the "Events" section. I'm not sure if it belongs in DOM at all, but I don't know of a better place really, so who knows... new WHATWG spec time?
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.
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.
IDL should be fine. I can't imagine this getting much bigger; new spec was mostly in jest.
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.
That is to say, if this remains fairly small long term, I don't like the idea of it being on its own. In DOM seems somewhat reasonable given that events are signals of sorts too, but it's pushing things a bit too of course.
And if this needs to be a non-IDL thing, perhaps Streams is a good place? It's also fits somewhat and Fetch has a dependency on that anyway, as will many other things.
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.
Note that it has a dependency on HTML via EventHandler, somewhat amusingly.
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'll defer to y'all about where you'd like this to live. I have no strong opinion, so long as it lives somewhere.