You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
Copy file name to clipboardExpand all lines: README.md
+86-63Lines changed: 86 additions & 63 deletions
Display the source diff
Display the rich diff
Original file line number
Diff line number
Diff line change
@@ -1,74 +1,79 @@
1
1
# js-async-cancellable
2
2
3
-
This library provides the ability to cancel asynchronous tasks. Cancelling asynchronous tasks was never standardised in JavaScript. This was due to the myriad complexity of cancellation illustrated by https://github.com/tc39/proposal-cancellation:
3
+
This library provides the ability to cancel asynchronous tasks. Cancelling
4
+
asynchronous tasks was never standardised in JavaScript. This was due to the
5
+
myriad complexity of cancellation illustrated by
6
+
https://github.com/tc39/proposal-cancellation:
4
7
5
-
> The following are some architectural observations provided by **Dean Tribble** on the [es-discuss mailing list](https://mail.mozilla.org/pipermail/es-discuss/2015-March/041887.html):
8
+
> The following are some architectural observations provided by **Dean Tribble**
> Promises are like object references for async; any particular promise might
10
-
> be returned or passed to more than one client. Usually, programmers would
11
-
> be surprised if a returned or passed in reference just got ripped out from
12
-
> under them _by another client_. this is especially obvious when considering
13
-
> a library that gets a promise passed into it. Using "cancel" on the promise
14
-
> is like having delete on object references; it's dangerous to use, and
15
-
> unreliable to have used by others.
14
+
> Promises are like object references for async; any particular promise might be
15
+
> returned or passed to more than one client. Usually, programmers would be
16
+
> surprised if a returned or passed in reference just got ripped out from under
17
+
> them _by another client_. this is especially obvious when considering a
18
+
> library that gets a promise passed into it. Using "cancel" on the promise is
19
+
> like having delete on object references; it's dangerous to use, and unreliable
20
+
> to have used by others.
16
21
>
17
22
> _Cancellation is heterogeneous_
18
23
>
19
24
> It can be misleading to think about canceling a single activity. In most
20
25
> systems, when cancellation happens, many unrelated tasks may need to be
21
-
> cancelled for the same reason. For example, if a user hits a stop button on
22
-
> a large incremental query after they see the first few results, what should
26
+
> cancelled for the same reason. For example, if a user hits a stop button on a
27
+
> large incremental query after they see the first few results, what should
23
28
> happen?
24
29
>
25
30
> - the async fetch of more query results should be terminated and the
26
31
> connection closed
27
-
> - background computation to process the remote results into renderable
28
-
> form should be stopped
29
-
> - rendering of not-yet rendered content should be stopped. this might
30
-
> include retrieval of secondary content for the items no longer of interest
31
-
> (e.g., album covers for the songs found by a complicated content search)
32
-
> - the animation of "loading more" should be stopped, and should be
33
-
> replaced with "user cancelled"
32
+
> - background computation to process the remote results into renderable form
33
+
> should be stopped
34
+
> - rendering of not-yet rendered content should be stopped. this might include
35
+
> retrieval of secondary content for the items no longer of interest (e.g.,
36
+
> album covers for the songs found by a complicated content search)
37
+
> - the animation of "loading more" should be stopped, and should be replaced
38
+
> with "user cancelled"
34
39
> - etc.
35
40
>
36
41
> Some of these are different levels of abstraction, and for any non-trivial
37
-
> application, there isn't a single piece of code that can know to terminate
38
-
> all these activities. This kind of system also requires that cancellation
39
-
> support is consistent across many very different types of components. But
40
-
> if each activity takes a cancellationToken, in the above example, they just
41
-
> get passed the one that would be cancelled if the user hits stop and the
42
-
> right thing happens.
42
+
> application, there isn't a single piece of code that can know to terminate all
43
+
> these activities. This kind of system also requires that cancellation support
44
+
> is consistent across many very different types of components. But if each
45
+
> activity takes a cancellationToken, in the above example, they just get passed
46
+
> the one that would be cancelled if the user hits stop and the right thing
47
+
> happens.
43
48
>
44
49
> _Cancellation should be smart_
45
50
>
46
51
> Libraries can and should be smart about how they cancel. In the case of an
47
-
> async query, once the result of a query from the server has come back, it
48
-
> may make sense to finish parsing and caching it rather than just
49
-
> reflexively discarding it. In the case of a brokerage system, for example,
50
-
> the round trip to the servers to get recent data is the expensive part.
51
-
> Once that's been kicked off and a result is coming back, having it
52
-
> available in a local cache in case the user asks again is efficient. If the
53
-
> application spawned another worker, it may be more efficient to let the
54
-
> worker complete (so that you can reuse it) rather than abruptly terminate
55
-
> it (requiring discarding of the running worker and cached state).
52
+
> async query, once the result of a query from the server has come back, it may
53
+
> make sense to finish parsing and caching it rather than just reflexively
54
+
> discarding it. In the case of a brokerage system, for example, the round trip
55
+
> to the servers to get recent data is the expensive part. Once that's been
56
+
> kicked off and a result is coming back, having it available in a local cache
57
+
> in case the user asks again is efficient. If the application spawned another
58
+
> worker, it may be more efficient to let the worker complete (so that you can
59
+
> reuse it) rather than abruptly terminate it (requiring discarding of the
60
+
> running worker and cached state).
56
61
>
57
62
> _Cancellation is a race_
58
63
>
59
64
> In an async system, new activities may be getting continuously scheduled by
60
65
> asks that are themselves scheduled but not currently running. The act of
61
-
> cancelling needs to run in this environment. When cancel starts, you can
62
-
> think of it as a signal racing out to catch up with all the computations
63
-
> launched to achieve the now-cancelled objective. Some of those may choose
64
-
> to complete (see the caching example above). Some may potentially keep
65
-
> launching more work before that work itself gets signaled (yeah it's a bug
66
-
> but people write buggy code). In an async system, cancellation is not
67
-
> prompt. Thus, it's infeasible to ask "has cancellation finished?" because
68
-
> that's not a well defined state. Indeed, there can be code scheduled that
69
-
> should and does not get cancelled (e.g., the result processor for a pub/sub
70
-
> system), but that schedules work that will be cancelled (parse the
71
-
> publication of an update to the now-cancelled query).
66
+
> cancelling needs to run in this environment. When cancel starts, you can think
67
+
> of it as a signal racing out to catch up with all the computations launched to
68
+
> achieve the now-cancelled objective. Some of those may choose to complete (see
69
+
> the caching example above). Some may potentially keep launching more work
70
+
> before that work itself gets signaled (yeah it's a bug but people write buggy
71
+
> code). In an async system, cancellation is not prompt. Thus, it's infeasible
72
+
> to ask "has cancellation finished?" because that's not a well defined state.
73
+
> Indeed, there can be code scheduled that should and does not get cancelled
74
+
> (e.g., the result processor for a pub/sub system), but that schedules work
75
+
> that will be cancelled (parse the publication of an update to the
76
+
> now-cancelled query).
72
77
>
73
78
> _Cancellation is "don't care"_
74
79
>
@@ -77,10 +82,10 @@ This library provides the ability to cancel asynchronous tasks. Cancelling async
77
82
> efforts". When a set of computations are cancelled, the party canceling the
78
83
> activities is saying "I no longer care whether this completes". That is
79
84
> importantly different from saying "I want to prevent this from completing".
80
-
> The former is broadly usable resource reduction. The latter is only
81
-
> usefully achieved in systems with expensive engineering around atomicity
82
-
> and transactions. It was amazing how much simpler cancellation logic
83
-
> becomes when it's "don't care".
85
+
> The former is broadly usable resource reduction. The latter is only usefully
86
+
> achieved in systems with expensive engineering around atomicity and
87
+
> transactions. It was amazing how much simpler cancellation logic becomes when
88
+
> it's "don't care".
84
89
>
85
90
> _Cancellation requires separation of concerns_
86
91
>
@@ -89,27 +94,45 @@ This library provides the ability to cancel asynchronous tasks. Cancelling async
89
94
> surprise if a library called for a cancellable activity (load this image)
90
95
> cancelled an unrelated server query just because they cared about the same
91
96
> cancellation event. I find it interesting that the separation between
92
-
> cancellation token and cancellation source mirrors that separation between
93
-
> a promise and it's resolver.
97
+
> cancellation token and cancellation source mirrors that separation between a
98
+
> promise and it's resolver.
94
99
>
95
100
> _Cancellation recovery is transient_
96
101
>
97
-
> As a task progresses, the cleanup action may change. In the example above,
98
-
> if the data table requests more results upon scrolling, it's cancellation
99
-
> behavior when there's an outstanding query for more data is likely to be
100
-
> quite different than when it's got everything it needs displayed for the
101
-
> current page. That's the reason why the "register" method returns a
102
-
> capability to unregister the action.
102
+
> As a task progresses, the cleanup action may change. In the example above, if
103
+
> the data table requests more results upon scrolling, it's cancellation
104
+
> behavior when there's an outstanding query for more data is likely to be quite
105
+
> different than when it's got everything it needs displayed for the current
106
+
> page. That's the reason why the "register" method returns a capability to
107
+
> unregister the action.
103
108
104
109
This library attempts to address each concern.
105
110
106
-
1. Cancel requests, not results - cancellation only affects the immediate promise and all downstream promises, not upstream promises unless explicitly configured via a signal handler
107
-
2. Cancellation is heterogenous - cancellation can be customised through a signal handler or an `AbortController`, this allows the user to define how cancellation should propagate through the application
108
-
3. Cancellation should be smart - during the `PromiseCancellable.then` binding, both the the fulfilled and rejected handler takes the `signal: AbortSignal`, here it is possible to customise the logic of fulfillment and rejection depending on the situation, therefore cancellation can be as smart as you want it to be
109
-
4. Cancellation is a race - this is achieved by using `AbortSignal`, one cannot ask if cancellation is finished, it is purely an event driven system
110
-
5. Cancellation is "don't care" - `PromiseCancellation.cancel` is purely advisory, the default behaviour is that the immediate promise is rejected early, however this can be customised, therefore the default intention is that the promise can be rejected with the cancellation reason, and means you don't care about the result anymore, it does not imply anything stronger than this
111
-
6. Cancellation requires separation of concerns - by supplying a signal handler or abort controller, it is possible to separate any concern
112
-
7. Cancellation recovery is transient - during the `PromiseCancellable.then`, `PromiseCancellable.catch` and `PromiseCancellable.finally`, the signal is passed in, it is possible to change how you cancel depending on what the current situation is.
111
+
1. Cancel requests, not results - cancellation only affects the immediate
112
+
promise and all downstream promises, not upstream promises unless explicitly
113
+
configured via a signal handler
114
+
2. Cancellation is heterogenous - cancellation can be customised through a
115
+
signal handler or an `AbortController`, this allows the user to define how
116
+
cancellation should propagate through the application
117
+
3. Cancellation should be smart - during the `PromiseCancellable.then` binding,
118
+
both the the fulfilled and rejected handler takes the `signal: AbortSignal`,
119
+
here it is possible to customise the logic of fulfillment and rejection
120
+
depending on the situation, therefore cancellation can be as smart as you
121
+
want it to be
122
+
4. Cancellation is a race - this is achieved by using `AbortSignal`, one cannot
123
+
ask if cancellation is finished, it is purely an event driven system
124
+
5. Cancellation is "don't care" - `PromiseCancellation.cancel` is purely
125
+
advisory, the default behaviour is that the immediate promise is rejected
126
+
early, however this can be customised, therefore the default intention is
127
+
that the promise can be rejected with the cancellation reason, and means you
128
+
don't care about the result anymore, it does not imply anything stronger than
129
+
this
130
+
6. Cancellation requires separation of concerns - by supplying a signal handler
131
+
or abort controller, it is possible to separate any concern
132
+
7. Cancellation recovery is transient - during the `PromiseCancellable.then`,
133
+
`PromiseCancellable.catch` and `PromiseCancellable.finally`, the signal is
134
+
passed in, it is possible to change how you cancel depending on what the
0 commit comments