Re: [whatwg/dom] Expose an `aborted` promise on AbortSignal? (#946)

Here's how I'd document the proposal, plus an alternative that's mostly based on @Jamesernator's:

---
**`abortSignal.doAbortable(callback)`**

`doAbortable` simplifies making an API abortable via an `AbortSignal`.

```js
const result = await signal.doAbortable(async (setAbortAction) => {
  // Do async work here.
  setAbortAction(() => {
    // Abort the async work here.
  });
});
```

`callback` is called immediately unless abort has been signalled, in which case `doAbortable` returns a promise rejected with an `AbortError`.

`callback` should return a promise that settles once the async work is complete. `doAbortable` returns a promise that resolves with return value of `callback`, or rejects with an `AbortError` if abort is signalled.

If abort is signalled, the _last_ callback passed to `setAbortAction` will be called. `setAbortAction` can be called with `undefined` if there are no longer any meaningful abort steps.

Example: An abortable that resolves with `'hello'` after 10 seconds:

```js
const controller = new AbortController();
const signal = controller.signal;

const promise = signal.doAbortable((setAbortAction) => {
  return new Promise((resolve) => {
    const timerId = setTimeout(() => resolve('hello'), 10_000);
    setAbortAction(() => clearTimeout(timerId));
  });
});
```

`promise` will resolve with `'hello'` after 10 seconds, unless `controller.abort()` is called within those 10 seconds, in which case `promise` rejects with an `AbortError`.

<details>
<summary>Implementation</summary>

```js
AbortSignal.prototype.doAbortable = function (callback) {
  if (this.aborted) throw new DOMException('', 'AbortError');
  let onAbort, listener;
  const setAbortAction = (c) => { onAbort = c };
  const promise = callback(setAbortAction);

  return Promise.race([
    new Promise((_, reject) => {
      listener = () => {
        onAbort?.();
        reject(new DOMException('', 'AbortError'));
      };
      this.addEventListener('abort', listener);
    }),
    promise,
  ]).finally(() => this.removeEventListener('abort', listener));
};
```

</details>

---

Alternatively, based on @Jamesernator's idea:

 **`abortSignal.doAbortable(callback)`**

`doAbortable` simplifies making an API abortable via an `AbortSignal`.

```js
const result = await signal.doAbortable(async (innerSignal) => {
  // Do async work here.
  innerSignal.addEventListener('abort', () => {
    // Abort the async work here.
  });
});
```

`callback` is called immediately unless abort has been signalled, in which case `doAbortable` returns a promise rejected with an `AbortError`.

`callback` should return a promise that settles once the async work is complete. `doAbortable` returns a promise that resolves with return value of `callback`, or rejects with an `AbortError` if abort is signalled.

`innerSignal` follows the outer signal, meaning it signals abort when the outer signal signals abort. However, it's only active for the duration of `callback`, which improves garbage collection.

Example: An abortable that resolves with `'hello'` after 10 seconds:

```js
const controller = new AbortController();
const signal = controller.signal;

const promise = signal.doAbortable((innerSignal) => {
  return new Promise((resolve) => {
    const timerId = setTimeout(() => resolve('hello'), 10_000);
    innerSignal.addEventListener('abort', () => clearTimeout(timerId));
  });
});
```

`promise` will resolve with `'hello'` after 10 seconds, unless `controller.abort()` is called within those 10 seconds, in which case `promise` rejects with an `AbortError`.

<details>
<summary>Implementation</summary>

```js
AbortSignal.prototype.doAbortable = function (callback) {
  if (this.aborted) throw new DOMException('', 'AbortError');
  const controller = new AbortController();
  const innerSignal = controller.signal;
  const promise = callback(innerSignal);
  let listener;

  return Promise.race([
    new Promise((_, reject) => {
      listener = () => {
        controller.abort();
        reject(new DOMException('', 'AbortError'));
      };
      this.addEventListener('abort', listener);
    }),
    promise,
  ]).finally(() => this.removeEventListener('abort', listener));
};
```

</details>


-- 
You are receiving this because you are subscribed to this thread.
Reply to this email directly or view it on GitHub:
https://github.com/whatwg/dom/issues/946#issuecomment-775093185

Received on Monday, 8 February 2021 11:57:10 UTC