W3C home > Mailing lists > Public > public-script-coord@w3.org > January to March 2015

Re: Cancellation architectural observations

From: Andrea Giammarchi <andrea.giammarchi@gmail.com>
Date: Mon, 2 Mar 2015 21:18:57 +0000
Message-ID: <CADA77mgemV2iqaGjgGUAF9RGyR7sOXo545RgCrZhZP1ncV2cmw@mail.gmail.com>
To: Ron Buckton <rbuckton@chronicles.org>
Cc: Dean Tribble <tribble@e-dean.com>, Kevin Smith <zenparsing@gmail.com>, "public-script-coord@w3.org" <public-script-coord@w3.org>, es-discuss <es-discuss@mozilla.org>
So this is my simplified view of the matter ... it already works, and it
aborts eventually with the ability to ignore the onabort callback.

The config object can have `onabort` that activates the "abort-ability",
the `onprogress` so that eventually this promise inside a generator can
still update UIs, and potentially any other sort of property but for demo
sake just `method` for GET, HEAD, PUT, POST and other requests.

```js
function fetch(url, config) {
  config || (config = {});
  var
    xhr = new XMLHttpRequest,
    promise = new Promise(function (res, rej) {
      xhr.addEventListener('error', function (pe) { rej(xhr); });
      xhr.addEventListener('load', function (pe) { res(xhr); });
      if (config.onabort)
        xhr.addEventListener('abort', config.onabort);
      if (config.onprogress)
        xhr.addEventListener('progress', config.onprogress);
      xhr.open(config.method || 'GET', url, true);
      xhr.send(null);
    })
  ;
  if (config.onabort)
    promise.abort = xhr.abort.bind(xhr);
  return promise;
}
```

abort example:
`fetch('?page', {onabort: console.warn.bind(console)}).abort();`

with progress too
`fetch('?page', {onabort: console.warn.bind(console), onprogress:
console.log.bind(console)}).abort();`

full request
`fetch('?page', {onabort: console.warn.bind(console), onprogress:
console.log.bind(console)}).then(console.log.bind(console));`

Why this code? Simply to somehow show that I am all for getting this right,
but to me it's also probably a simpler matter than it looks like, specially
for cases where cancel or abort is meant and needed.

Best Regards


On Mon, Mar 2, 2015 at 7:45 PM, Ron Buckton <rbuckton@chronicles.org> wrote:

>  ​
>
> In light of *Async Functions* in ES7, it may make sense to separate the
> abstractions between promises and cancellation. Promises and cancellation
> signals have different use cases:
>
>
>  *Promises*
>
>    - Promises are *consumed* by the caller and *produced* by the callee.
>    - Observation a promise resolution can only happen in a *later * turn.
>    - The consumer of a promise *cannot* directly resolve the promise.
>
> *Cancellation*
>
>    - Cancellation signals are *produced* by the caller and * consumed* by
>    the callee.
>    - Observation a cancellation signal must happen *immediately*.
>    - The consumer of a cancellation token *cannot* directly cancel the
>    token.
>
> *API Proposal:*
>
>
>  class CancellationTokenSource {
>   /** Create a new CTS, optionally with an iterable of linked cancellation
> tokens. */
>   constructor(linkedTokens?: Iterable<CancellationToken>);
>
>   /** Gets the cancellation token for this source. */
>   get token(): CancellationToken;
>
>   /** Cancels the source and sends a cancellation signal with an optional
> reason (default Error("Operation canceled")). */
>   cancel(reason?: any): void;
>
>   /** Cancels the source after a delay (in milliseconds), with an optional
> reason. */
>   cancelAfter(delay: number, reason?: any): void;
>
>   /** Prevents any possible future cancellation of the source and removes
> all linked registrations. */
>   close(): void;
> }
>
> class CancellationToken {
>   /** Gets a cancellation token that can never be canceled. */
>   static get default(): CancellationToken;
>   /** Gets a value indicating whether the cancellation signal was sent. */
>   get canceled(): boolean;
>   /** If canceled, gets the reason for cancellation if provided;
> otherwise, returns `undefined`. */
>   get reason(): any;
>   /** If canceled, throws either the reason or a general “Operation
> Canceled” error. */
>   throwIfCanceled(): void;
>   /**
>     * Registers a callback to execute immediately when a cancellation
> signal is received.
>     * The callback can be removed using the `unregister` method of the
> return value.
>     */
>   register(callback: (reason: any) => void): { unregister(): void };
> }
>
>
>  *Usage (Abort):*
>
>
>  ```
> // aborts and throws error when canceled
> function fetchAsync(url, cancellationToken = CancellationToken.default) {
>   return new Promise((resolve, reject) => {
>     cancellationToken.throwIfCanceled();
>     var xhr = new XMLHttpRequest();
>     var registration = cancellationToken.register(() => xhr.abort());
>     xhr.open("GET", url, /*async*/ true);
>     xhr.onload = event => {
>         registration.unregister();
>         resolve(xhr.responseText);
>     };
>     xhr.onerror = event => {
>         registration.unregister();
>         reject(xhr.statusText);
>     }
>     xhr.send(null);
>   });
> }
>
> fetchAsync(...).then(...); // as expected
>
> var cts1 = new CancellationTokenSource();
> fetchAsync(..., cts1.token).catch(...);
> cts1.cancel(new Error("Operation Canceled"); // .catch gets the error and
> the xhr is aborted.
>
>
>  *Usage (Ignore):*
>
>
>  // ignore operation/stop processing
> async function startTicker(receiveSymbol, cancellationToken =
> CancellationToken.default) {
>     while (!cancellationToken.canceled) {
>         var symbols = await fetchSymbols();
>         for (var symbol of symbols) {
>             receiveSymbol(symbol);
>         }
>     }
> }
>
> var stopTicker = new CancellationTokenSource();
> stopTicker.cancelAfter(5 * 60 * 1000); // stop after 5 minutes.
> startTicker(..., stopTicker.token).catch(...); // .catch only gets error
> from `fetchSymbols`.
>
>
>  Ron
>  ------------------------------
> *From:* es-discuss <es-discuss-bounces@mozilla.org> on behalf of Dean
> Tribble <tribble@e-dean.com>
> *Sent:* Monday, March 02, 2015 1:25 PM
> *To:* Kevin Smith
> *Cc:* public-script-coord@w3.org; es-discuss
> *Subject:* Re: Cancellation architectural observations
>
>   On Mon, Mar 2, 2015 at 6:32 AM, Gray Zhang <otakustay@icloud.com> wrote:
>
>>  +1 to the ignore term, I’ve opened an issue about it in
>> https://github.com/promises-aplus/cancellation-spec/issues/14
>>
> I have little attachment to any term, but there's value in keeping
> terminology that has years of investment and use in other contexts. However
> "ignore" also has the wrong sense, because it implies that the computation
> completes anyway. That can be accomplished more easily by simply dropping
> the promise.
>
>>  IMO the term cancel(or abort) and ignore are totally different things,
>> the former one means “do not continue, stop it right now” and the “stop”
>> state should be broadcast to everyone who is interested in the work, while
>> the latter means “I don’t care about the result anymore, just play it as
>> you like”, it means the async progress can be continued
>>
>  This goes back to some of the observations above: you cannot stop it
> "right now" because async notification is not synchronous; indeed the
> operation may already be complete before you stop it. Thus consumers of the
> result of a cancellable request need to be able to handle either successful
> completion or the cancelled state (which just looks like any other error
> that prevented completion).  Attempting broadcast to "everyone" adds
> complexity and resources that are needed only in the rare cancellation
> case. It's typically not only not worth the software complexity, but not a
> good idea. When you cancel a print job, the document editor should make
> best efforts in the background to stop requesting fonts, stop laying out
> print pages, stop spitting out pages on the printer, etc. but most
> importantly, it should start paying attention to my new edits and hang
> waiting for everything that might be involved in printing to wrap itself up.
>
>>  In practice both scenario are commonly seen, we may abort a resource
>> fetch in order to save bandwidth and opened connections, or we may in other
>> side just ignore it since continue to complete the fetch can result in a
>> local cache, which speeds up our fetch next time
>>
> The resource point is important. That's the "don't care" scenario, not the
> "abort" scenario. It's the request processor that knows what cleanup is
> worth the effort. The initiator of the request only knows they don't care
> about the result anymore.
>
> _______________________________________________
> es-discuss mailing list
> es-discuss@mozilla.org
> https://mail.mozilla.org/listinfo/es-discuss
>
>
Received on Monday, 2 March 2015 21:19:24 UTC

This archive was generated by hypermail 2.3.1 : Monday, 2 March 2015 21:19:25 UTC