In this section, we'll cover how we can use `action` as a safe alternative to
the [Promise constructor][promise-constructor].

## Example: Sleep

Let's revisit our sleep operation from the [introduction to
operations](../operations):

```js
export function sleep(duration: number): Operation<void> {
  return action((resolve) => {
    let timeoutId = setTimeout(resolve, duration);
    return () => clearTimeout(timeoutId);
  });
}
```

As we saw, no matter how the sleep operation ends, it always executes the
`clearTimeout()` on its way out.

If we wanted to replicate our `sleep()` functionality with promises, we'd need
to do something like accept an [`AbortSignal`][abort-signal] as a second
argument to `sleep()`, and then use it to prevent our event-loop callback from
leaking:

```js
export function sleep(duration, signal) {
  return new Promise((resolve) => {
    if (signal.aborted) {
      resolve();
    } else {
      let timeoutId = setTimeout(resolve, duration);
      signal.addEventListener("abort", () => clearTimeout(timeoutId));
    }
  });
}
```

This functions properly, but is ham-fisted. Not only is the
implementation non-obvious, but it's also cumbersome to use in
practice because it involves first creating a signal, passing it
around explicitly to everything, and then finally firing it manually when the
program is over:

```js
let controller = new AbortController();
let { signal } = controller;

await Promise.all([sleep(10, signal), sleep(1000, signal)]);

controller.abort();
```

With an action on the other hand, we get all the benefit as if
an abort signal was there without sacrificing any clarity in achieving it.

## Action Constructor

The [`action()`][action] function provides a callback based API to create Effection operations.
You don't need it all that often, but when you do it functions almost exactly like the
[promise constructor][promise-constructor]. To see this, let's
use [one of the examples from MDN](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Promise/Promise#examples)
that uses promises to make a crude replica of the global [`fetch()`][fetch]
function. It manually creates an XHR, and hooks up the `load` and `error` events
to the `resolve` and `reject` functions respectively.

```js
async function fetch(url) {
  return await new Promise((resolve, reject) => {
    let xhr = new XMLHttpRequest();
    xhr.open("GET", url);
    xhr.onload = () => resolve(xhr.responseText);
    xhr.onerror = () => reject(xhr.statusText);
    xhr.send();
  });
}
```

Consulting the [Async Rosetta Stone](async-rosetta-stone), we can substitute the async
constructs for their Effection counterparts to arrive at an (almost) line for line
translation. The only significant difference is that unlike the promise constructor, an
action constructor _must_ return a "finally" function to exit the action.

```js
function* fetch(url) {
  return yield* action((resolve, reject) => {
    let xhr = new XMLHttpRequest();
    xhr.open("GET", url);
    xhr.onload = () => resolve(xhr.responseText);
    xhr.onerror = () => reject(xhr.statusText);
    xhr.send();
    return () => {}; // "finally" function place holder.
  });
}
```

While this works works every bit as well as the promise based
implementation, it turns out that the example from MDN has a subtle
bug. In fact, it's the same subtle bug that afflicted the "racing
sleep" example in the [introduction to
operations](../operations#cleanup). If
we no longer care about the outcome of our `fetch` operation, we will
"leak" its http request which will remain in flight until a response
is received. In the example below it does not matter which web request
"wins" the race to fetch the current weather, our process cannot exit
until _both_ requests are have received a response.

```js
await Promise.race([
  fetch("https://openweathermap.org"),
  fetch("https://open-meteo.org"),
]);
```

With Effection, this is easily fixed by calling `abort()` in the finally function to
make sure that the request is cancelled when it is either resolved, rejected, or
passes out of scope.

```js {8} showLineNumbers
function* fetch(url) {
  return yield* action((resolve, reject) => {
    let xhr = new XMLHttpRequest();
    xhr.open("GET", url);
    xhr.onload = () => resolve(xhr.responseText);
    xhr.onerror = () => reject(xhr.statusText);
    xhr.send();
    return () => {
      xhr.abort();
    }; // called in all cases
  });
}
```

> 💡Almost every usage of the [promise concurrency primitives][promise-concurrency]
> will contain bugs born of leaked effects.

[abort-signal]: https://developer.mozilla.org/en-US/docs/Web/API/AbortSignal
[fetch]: https://developer.mozilla.org/en-US/docs/Web/API/fetch
[promise-constructor]: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Promise/Promise
[promise-concurrency]: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Promise#promise_concurrency
[cleanup]: ./operations#cleanup
[scope]: ./scope
[spawn-suspend]: ./spawn#suspend
[action]: /api/v4/action
[all]: /api/v4/all
