# Cooperative async function

**URL:** <https://es.discourse.group/t/cooperative-async-function/975>\
**Category:** 💡 Ideas\
**Created:** [September 7, 2021, 4:26am UTC](https://es.discourse.group/t/cooperative-async-function/975 "2021-09-07T04:26:56Z")\
**Posts on this page:** 6\
**Page:** 1

<div class="post-metadata">

**Author:** ![JackWorks](https://yyz2.discourse-cdn.com/free1/user_avatar/es.discourse.group/jackworks/32/260_2.png) [@JackWorks](https://es.discourse.group/u/JackWorks)\
**Post date:** [September 7, 2021, 4:26am UTC](https://es.discourse.group/t/cooperative-async-function/975/1 "2021-09-07T04:26:56Z")

</div>

## Motivation

Rust-style async function: Async functions that act in the Poll model run therefore user-land code can decide when to continue the execution, or even cancel the execution.

## Design

I propose a syntax sugar to the generator function. In the old times, we use [co](http://npmjs.com/co) and generator functions to simulate the async functions.

```js
import co from 'co'

co(function* () {
    yield Promise.resolve(true)
    // eq to await Promise.resolve(true)
    return 1
}).then(console.log)

```

`co` is the userland scheduler and the generator is a cooperative async function. This is an old-time example.

[WICG/scheduling-apis](https://github.com/WICG/scheduling-apis) is the next generation Web scheduler API, here is an example of my proposal (to act with scheduling APIs).

```js
lazy async function task() {
    const a = await fetch("./task")
    const b = await a.json()
    return b.tasks
}
const res = scheduler.postTask(task, { priority: 'background' });

```

The scheduler can "pause" the async function to continue if there is a high-priority task that comes in. With this manner, this syntax is a natural replacement of `scheduler.yield()` in the WICG/Scheduling-API proposal.

### A userland example: cancellable async task

In many frameworks like React, Vue, and Angular, components have a "life circle". Async tasks should be canceled when the component is dismounted. This can be done with `AbortSignal` or manually check but there are much noises inside the code.

For example:

```js
useEffect(() => {
    let stop = false
    (async () => {
        const data = await fetch('./data')
        const json = await data.json()
        if (stop) return
        setState(json)
    })()
    return () => stop = true
})

```

With an `AbortSignal`-friendly user land API (not real in React) it can be simpler:

```js
useEffect(async signal => {
    const data = await fetch('./data', { signal })
    const json = await data.json()
    if (signal.aborted) return
    setState(json)
})

```

With this proposal, it can be much simpler:

```js
useEffect(lazy async signal => {
    const data = await fetch('./data', { signal })
    const json = await data.json()
    setState(json)
})

```

As you can see, there is no need to check if the signal has expired. Because the userland scheduler `useEffect` will cancel the running async function.

## Semantics: How it works

The `lazy` modifier on the async function creates a lazy async function. When it is called, it behaves like a `Generator<Promise<any>>`.

```js
lazy async function task() {
    console.log("start")
    await true
    console.log("then")
    return fetch("/")
}

const t = task()
// t: Generator<suspended>
$ = t.next()
// Log: "start"
// { done: false, value: Promise<true> }
await $.value
$ = t.next()
// Log: "then"
// { done: false, value: Promise<fetch> }
await $.value
$ = t.next()
// { done: true, value: Resopnse }

```

- The value passed into the `next()` WILL BE ignored. The `await` value will always be the result of the promise. The scheduler can only control the continue or not. It cannot replace the `await` result.
- Return a `Promise` will be treated as `return await $ret`

### Interact with other syntaxes

Await lazy async function in a normal async function:

```js
async function a() {
    await Promise.from(b())
}
lazy asnyc function b() {}

```

A new API like `Promise.from` will convert a `Generator<Promise<Yield>, Return>` into `Promise<Return>`.

Await lazy async function in another lazy async function

```js
lazy async function a() {
    await b()
}
lazy async function b() {}

```

No need to do anything. It works like a `yield* $value`, delegate all steps to the scheduler.

## Why not generators?

- Semantics do not match

## Benefits

- Stoppable async functions
- Native scheduler (the WICG one) or the userland scheduler (React) can schedule tasks that take priority knowledge in mind.

---

<div class="post-metadata">

**Author:** ![claudiameadows](https://yyz2.discourse-cdn.com/free1/user_avatar/es.discourse.group/claudiameadows/32/126_2.png) [@claudiameadows](https://es.discourse.group/u/claudiameadows)\
**Post date:** [September 7, 2021, 6:25am UTC](https://es.discourse.group/t/cooperative-async-function/975/2 "2021-09-07T06:25:59Z")

</div>

What does this provide that [GitHub - tc39/proposal-cancellation: Proposal for a Cancellation API for ECMAScript](https://github.com/tc39/proposal-cancellation/) couldn't? Also, I'd suggest you suggest this alternate proposal there as well, for better visibility.

---

<div class="post-metadata">

**Author:** ![claudiameadows](https://yyz2.discourse-cdn.com/free1/user_avatar/es.discourse.group/claudiameadows/32/126_2.png) [@claudiameadows](https://es.discourse.group/u/claudiameadows)\
**Post date:** [September 7, 2021, 6:55am UTC](https://es.discourse.group/t/cooperative-async-function/975/3 "2021-09-07T06:55:33Z")

</div>

Oh, and I probably should've mentioned: [I have my own alternative idea I created a while back](https://github.com/tc39/proposal-cancellation/issues/24) that also offers potential solutions.

I like the idea of making it cooperative, but I can't say I'm a fan of making it quite _that_ opt-in. I'd rather only opt into it wherever I'm adding cancellation semantics, because 1. that's less code modification (less surface area for bugs) and 2. I don't need to wait for whatever library/framework/runtime function I'm using to be updated to be aware of the feature for me to start using cancellation support in almost all cases. (It's actually one of my biggest complaints about abort signals, too, after having used them quite a bit - I often need to figure out ways to plumb the token through code, integration can get boilerplatey, and it's easy to just forget to wire it up.)

---

<div class="post-metadata">

**Author:** ![Inori-Lover](https://yyz2.discourse-cdn.com/free1/user_avatar/es.discourse.group/inori-lover/32/998_2.png) [@Inori-Lover](https://es.discourse.group/u/Inori-Lover)\
**Post date:** [September 7, 2021, 9:38am UTC](https://es.discourse.group/t/cooperative-async-function/975/4 "2021-09-07T09:38:08Z")

</div>

cancel is not the only purpose; it more like giving control out

---

<div class="post-metadata">

**Author:** ![lightmare](https://yyz2.discourse-cdn.com/free1/user_avatar/es.discourse.group/lightmare/32/843_2.png) [@lightmare](https://es.discourse.group/u/lightmare)\
**Post date:** [September 7, 2021, 10:20am UTC](https://es.discourse.group/t/cooperative-async-function/975/5 "2021-09-07T10:20:10Z")

</div>

> [@JackWorks](#):
>
> Why not generators?
> 
> - Semantics do not match

From the examples you showed, generators make perfect sense to me. Imagine you had this:

```javascript
useEffect(function*() {
    const data = yield fetch('./data')
    const json = yield data.json()
    setState(json)
})

```

It yields control back to the scheduler, giving it an async task you need fulfilled before continuing. The scheduler will send you the result once it's ready, unless cancelled.

---

<div class="post-metadata">

**Author:** ![theScottyJam](https://yyz2.discourse-cdn.com/free1/user_avatar/es.discourse.group/thescottyjam/32/616_2.png) [@theScottyJam](https://es.discourse.group/u/theScottyJam)\
**Post date:** [September 7, 2021, 1:30pm UTC](https://es.discourse.group/t/cooperative-async-function/975/6 "2021-09-07T13:30:38Z")

</div>

One advantage of using a generator is that you can distinguish between points that are allowed to exit, and points that should ignore a cancel token. For example:

```javascript
async function*(cancelToken) {
  // These can be canceled
  const data1 = yield fetchSomeData1(cancelToken)
  const data2 = yield fetchSomeData2(data1.x, cancelToken)
  // At this point, it should not be canceled
  await animatSomethingIn(data1)
  await animateSomethingElseIn(data2)
}

```
