Aborting an async function

# Bradley Meck (9 years ago)

Right now async functions are dependent on being Promise based. However, sometimes we want to allow disjoint errors ala:

async function fetchJSON(url) {
  let client = new Client();

  // ... we aren't in the async function stack here ...
  client.on('error', abort?);

  // ... await get and return json body
}

My questions is, how can we setup a function to abort a pending async function?

We can almost emulate this behavior in task runners by using Generator.prototype.return with the caveat that it cannot be called while a generator is running (we can use non-preemptive cancellation for now).

I think this is somewhat important for existing libraries, and the DOM.

# Benjamin (Inglor) Gruenbaum (9 years ago)

My questions is, how can we setup a function to abort a pending async function?

That's an interesting question. Cancellation of promises is something that has been debated several times (not sure if at all in esdiscuss). There is a specification but there are many objections to it and some think it complects promises. Domenic is probably up to date on this.

In your case you have several alternatives with the current API:

  • You can return a rejected promise whenever a client method gets invoked if it is in error state.

  • You can use promises directly rather than an async function which would probably give you finer grained control here.

  • You can pass a token into clients' methods ala c#'s CancellationToken and invoke it on errors to signal something bad happened from the outside.

# Brendan Eich (9 years ago)

Benjamin (Inglor) Gruenbaum wrote:

Cancellation of promises is something that has been debated several times (not sure if at all in esdiscuss).

Many times:

www.google.com/search?q=site%3Aesdiscuss.org cancel promise

Google site: search is your friend.

# Bradley Meck (9 years ago)

I might be out of the loop but I haven't seen anything that lets me follow that this is on track / something that ES7 is going to have. Is there a more definitive place than just google + guessing to see which topic is the one currently in favor?

# Benjamin (Inglor) Gruenbaum (9 years ago)

Thanks, in case anyone is wondering this is the relevant result: esdiscuss.org/topic/future-cancellation

# Bradley Meck (9 years ago)

Made a run through of using purely promises to achieve this result and it ends up with nesting and an extra try/catch which is less than stellar:

let fetchish = function () {
  return new Promise((f,r) => {
    // technically we need a try catch around all the functions in here to act like an async function, but lets skip some for brevity
    let client = new Client();
    client.on('error', r);

    client.get().then((v) => {
      let body;
      try {
        body = JSON.parse(v);
      }
      catch (e) { r(e); return; }
      f(body);
    }, (e) => r(e));
  });
}

We can track when client is in an error and keep a list of pending promises to reject, though not all promise consumers will want to reject when any error occurs, so the pure Promises solution is going to win in that case.

And the cancellation token is just like a deferred? Where you pass a rejection function around outside of the promise initializer?

# Kevin Smith (9 years ago)

I would think that a hypothetical client.get method would reject when an error occurred, so that you wouldn't need to attach an "error" handler.

That said, you can also freely mix promises with await and async:

async function fetchish() {
    let text = await new Promise((resolve, reject) => {
        let client = new Client;
        client.on("error", reject);
        resolve(client.get());
    });

    return JSON.parse(text);
}
# Andrea Giammarchi (9 years ago)

to be honest that looks more like an all-in (good old events in the house) ... put a generator somewhere, please! </ignore-me>

I wonder if actually fetchish shouldn't be directly a Promise instead, feels like overlapping. Just random thoughts, curious to see more mixed async patterns in the real world

Best

# Benjamin (Inglor) Gruenbaum (9 years ago)

Yes, the non-overlapping way to write it IMO would be to isolate the rejection on errors to a function:

let watchError = (client) => new Promise((resolve, reject) => client.on("error", reject));

// as a plain promise
function fetchish(){
    let client = new Client;
    return Promise.race([watchError(client), client.get()]).then(JSON.parse));
}

// as an async function

async function fetchish(){
     let client = new Client();
     let result = await Promise.race([client.get(), watchError(client)]);
     return JSON.parse(result);
}
# Domenic Denicola (9 years ago)

Yep, the first thing I noted with Bradley’s example is that there were some pretty extraneous try/catches. Everything inside new Promise that throws an error will cause the promise to reject, so no try/catch needed there. And similarly inside a promise handler you should just let the errors be caught.

At the risk of turning this into code-review-discuss, my take on Bradley's function, without using async/await:

function fetchish() {
  return new Promise((res, rej) => {
    const client = new Client();
    client.on('error', rej);

    resolve(client.get());
  })
  .then(JSON.parse)
  .then(f);
}

(last few lines could equivalently be replaced by resolve(client.get().then(b => f(JSON.parse(b)))) if you'd like.)

--

Regarding cancelation, this has definitely been a desired feature for a long time. However I'm hesitant to dive into trying to standardizing something given that there are several competing ideas and no clear winner. (My leading candidates: a single-consumer consumer-cancelable Task<T> counterpart to Promise<T>; something similar to this old Promises/A+ discussion, which I think is similar to Bluebird 2.x; something built on that but more complicated, including support for "forking" and similar; C#-style cancelation tokens; ...)

I guess it seems likely that whatever we do will work for between 80-95% of cases and the remaining 5-20% will be unhappy. I think there's a lot of work to do to get that toward 95% instead of 80% though, and someone needs to champion it through that gauntlet.

# Benjamin (Inglor) Gruenbaum (9 years ago)

Bluebird is actually overhauling cancellation in 3.0 - petkaantonov/bluebird#415 for what it's worth we're not agreeing about syntax or how to make it better either :)

For what little my opinion is worth here compared to the other people involved I'm favouring the single consumer -consumer cancellable counterpart for promise because of the arguments Kris and Domenic made about the inherent issues of multiple consumer cancellation.