# Zero-overhead Async/Await

**URL:** <https://es.discourse.group/t/zero-overhead-async-await/2117>\
**Category:** 💡 Ideas\
**Tags:** proposal\
**Created:** [August 23, 2024, 1:34pm UTC](https://es.discourse.group/t/zero-overhead-async-await/2117 "2024-08-23T13:34:40Z")\
**Posts on this page:** 14\
**Page:** 1

<div class="post-metadata">

**Author:** ![streamich](https://yyz2.discourse-cdn.com/free1/user_avatar/es.discourse.group/streamich/32/2194_2.png) [@streamich](https://es.discourse.group/u/streamich)\
**Post date:** [August 23, 2024, 1:34pm UTC](https://es.discourse.group/t/zero-overhead-async-await/2117/1 "2024-08-23T13:34:40Z")

</div>

I would like to propose "zero-overhead async/await" syntax. Zero-overhead would essentially mean that async code is achieved by a single callback, instead of current Promise-based code which is 2 callbacks + object.

The syntax would very much look similar to the existing async/await syntax but would operate on top of callbacks instead of promises.

Await a callback-powered async function:

```js
await cb fs.readFile('a.txt.', cb);

```

Await callback-powered async function, where the last "callback" argument is implicit:

```js
await _ fs.readFile('a.txt');

```

Create an async function powered by callback, note the `async` keyword in place of the `callback` argument:

```js
function readFile(filename, async) {
  // ...
  return await _ napi.binding.read(filename);
}

```

The above `readFile` method, can be called using the regular callback-based JavaScript:

```javascript
readFile('a.txt.', (error, value) => {
  if (error) {
    // ...
  } else {
    // ...
  }
});

```

Or using the zero-overhead callback-based async/await (when in async context):

```javascript
try {
  let value = await _ readFile('a.txt');
  // ...
} catch (error) {
  // ...
}

```

More on the proposal: [Zero-overhead Async/Await - DEV Community](https://dev.to/streamich/asyncawait-at-the-speed-of-callbacks-2mkp)

---

<div class="post-metadata">

**Author:** ![aclaymore](https://yyz2.discourse-cdn.com/free1/user_avatar/es.discourse.group/aclaymore/32/501_2.png) [@aclaymore](https://es.discourse.group/u/aclaymore)\
**Post date:** [August 24, 2024, 6:55am UTC](https://es.discourse.group/t/zero-overhead-async-await/2117/2 "2024-08-24T06:55:11Z")

</div>

Hi @streamich, welcome!

It sounds like the primary motivator here is performance, if so an experiment that shows this would likely be helpful in determining if it's worth pursuing. While callbacks on their own may be faster, there would still be overhead with this syntax.

That said, I'm not sure how much appetite there is for introducing a 3rd way to do continuations, so there is a chance that even if an experiment showed this was faster that may not mean it would advance.

Also, are there other languages that have similar syntax? That can also be interesting to analyse.

---

<div class="post-metadata">

**Author:** ![streamich](https://yyz2.discourse-cdn.com/free1/user_avatar/es.discourse.group/streamich/32/2194_2.png) [@streamich](https://es.discourse.group/u/streamich)\
**Post date:** [August 24, 2024, 7:44am UTC](https://es.discourse.group/t/zero-overhead-async-await/2117/3 "2024-08-24T07:44:47Z")

</div>

> While callbacks on their own may be faster, there would still be overhead with this syntax.

What would be the overhead of the syntax?

---

<div class="post-metadata">

**Author:** ![aclaymore](https://yyz2.discourse-cdn.com/free1/user_avatar/es.discourse.group/aclaymore/32/501_2.png) [@aclaymore](https://es.discourse.group/u/aclaymore)\
**Post date:** [August 24, 2024, 8:09am UTC](https://es.discourse.group/t/zero-overhead-async-await/2117/4 "2024-08-24T08:09:28Z")

</div>

> [@streamich](#):
>
> `await cb fs.readFile('a.txt.', cb);`

This would still have to schedule the continuation into a new tick, because there is no guarantee that the callback is being called on a fresh stack. ReadFile could be:

```javascript
function readFile(path, cb) {
  cb();
  console.log("after cb");
}

```

---

<div class="post-metadata">

**Author:** ![streamich](https://yyz2.discourse-cdn.com/free1/user_avatar/es.discourse.group/streamich/32/2194_2.png) [@streamich](https://es.discourse.group/u/streamich)\
**Post date:** [August 24, 2024, 9:25am UTC](https://es.discourse.group/t/zero-overhead-async-await/2117/5 "2024-08-24T09:25:51Z")

</div>

> This would still have to schedule the continuation into a new tick, [..]

The "zero-overhead" abstraction means there would be no "scheduling" and no microtask queue involved. If the `cb` is called in the same stack, then the corresponding `await` will resume in the same stack.

---

<div class="post-metadata">

**Author:** ![streamich](https://yyz2.discourse-cdn.com/free1/user_avatar/es.discourse.group/streamich/32/2194_2.png) [@streamich](https://es.discourse.group/u/streamich)\
**Post date:** [August 24, 2024, 9:57am UTC](https://es.discourse.group/t/zero-overhead-async-await/2117/6 "2024-08-24T09:57:41Z")

</div>

Comparison to promises and some behavior description:

- Promises allow to fanout the result to multiple subscribers, the zero-overhead async/await pushes the result only to a single callback.
  - No overhead of multiple subscribers.

- A promise result is stored in an object, here the result is not stored anywhere, it is immediately pushed to the callback.
  - No overhead of extra object construction.

- A promise always resolves in the next stack, the zero-overhead async/await can resume continuation in the next stack, but also it could be the same stack.
  - No overhead of scheduling, no queue required.
  - No penalty of waiting until the next stack frame, if continuation possible in the current stack frame.

- Promises enforce that the subscription callbacks are executed at most once. The zero-overhead async/await does not provide such guarantee.
  - No overhead of tracking if the callback was already filled.

---

<div class="post-metadata">

**Author:** ![aclaymore](https://yyz2.discourse-cdn.com/free1/user_avatar/es.discourse.group/aclaymore/32/501_2.png) [@aclaymore](https://es.discourse.group/u/aclaymore)\
**Post date:** [August 24, 2024, 10:28am UTC](https://es.discourse.group/t/zero-overhead-async-await/2117/7 "2024-08-24T10:28:55Z")

</div>

> [@streamich](#):
>
> the `cb` is called in the same stack, then the corresponding `await` will resume in the same stack.

The issue there is that it may lead to unexpected execution semantics:

```javascript
function f(cb) {
  try {
    cb();
  } catch {}
}

await cb f(cb);
throw new Error();

```

the error is actually being thrown to `f`.

While promise can have over head compared to direct callbacks, that overhead is mostly there to make the execution order easier to reason about.

---

<div class="post-metadata">

**Author:** ![streamich](https://yyz2.discourse-cdn.com/free1/user_avatar/es.discourse.group/streamich/32/2194_2.png) [@streamich](https://es.discourse.group/u/streamich)\
**Post date:** [August 24, 2024, 10:50am UTC](https://es.discourse.group/t/zero-overhead-async-await/2117/8 "2024-08-24T10:50:21Z")

</div>

I guess that would be something to live with as it exists with the current callback code as well.

The zero-overhead syntax is not about solving callback semantics. It is about removing callback hell (indentation), while still keeping zero overhead.

---

<div class="post-metadata">

**Author:** ![aclaymore](https://yyz2.discourse-cdn.com/free1/user_avatar/es.discourse.group/aclaymore/32/501_2.png) [@aclaymore](https://es.discourse.group/u/aclaymore)\
**Post date:** [August 26, 2024, 6:55am UTC](https://es.discourse.group/t/zero-overhead-async-await/2117/9 "2024-08-26T06:55:13Z")

</div>

> [@streamich](#):
>
> as it exists with the current callback code as well.

With existing callback code it is more visually evident which portion of code is the callback, making it easier to understand where a `throw` might be caught

---

<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:** [August 31, 2024, 6:47am UTC](https://es.discourse.group/t/zero-overhead-async-await/2117/10 "2024-08-31T06:47:45Z")

</div>

What do you propose for this?

```js
function func(cb) {
    doSomethingAsync(() => {
        console.log("1")
        cb()
        console.log("2")
        cb()
        console.log("3")
    })
}

await cb func(cb)
console.log("next")

```

And what about this?

```js
function func(cb) {
    console.log("1")
    cb()
    console.log("2")
    cb()
    console.log("3")
}

await cb func(cb)
console.log("next")

```

---

<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:** [August 31, 2024, 6:48am UTC](https://es.discourse.group/t/zero-overhead-async-await/2117/11 "2024-08-31T06:48:11Z")

</div>

The main concern around maybe-sync code is race conditions resulting in subtle state bugs. It's extremely tricky to catch those errors, and in a couple of those bugs in sometimes-sync code, I had to resort to informal [process networks](https://en.wikipedia.org/wiki/Kahn_process_networks) and [actor](https://en.wikipedia.org/wiki/Actor_model) messaging graphs just to find where the state bug even was. If everything's async, it's far simpler to work with.

Also, regarding performance, runtimes that allow concurrent use of the same runtime context (like [WebKit](https://webkit.org/blog/7846/concurrent-javascript-it-can-work/) but not V8) allow you to allocate objects and resolve promises off the main thread. This avoids some of the performance impact when working with event loops, since you no longer need to queue for a lock just to queue up the promise resolution's reaction job.

---

<div class="post-metadata">

**Author:** ![streamich](https://yyz2.discourse-cdn.com/free1/user_avatar/es.discourse.group/streamich/32/2194_2.png) [@streamich](https://es.discourse.group/u/streamich)\
**Post date:** [September 17, 2024, 1:36pm UTC](https://es.discourse.group/t/zero-overhead-async-await/2117/12 "2024-09-17T13:36:37Z")

</div>

> What do you propose for this?

@claudiameadows it would do the same as what a function with callback would do. The output would be, in both cases:

```javascript
1
next
2
next
3

```

The idea is that the syntax transforms the code to something like this:

```js
func(() => {
  console.log("next")
})

```

The idea is to remove the callback hell while introducing zero-overhead. If extra security guarantees/convenience are needed, the developer can always choose to use `Promise` instead.

---

<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 18, 2024, 1:22pm UTC](https://es.discourse.group/t/zero-overhead-async-await/2117/14 "2024-09-18T13:22:37Z")

</div>

Why is their concern about the overhead of promises in the first place? Do you have an example where promise overhead was a significant bottleneck and you had to switch to regular callbacks as a result?

From what's been discussed, it sounds like the overhead can fall into one of three categories:

- The cpu overhead of actually using a promise
- The memory overhead of allocating the promise object
- The fact that if you use a promise with a synchronous task, you're force to wait until the next tick before the promise resolves.

All of these sound insignificant if the promises are being used for their intended purpose - scheduling work that needs to be done asynchronously. 1. The amount of time it takes for the engine to run internal promise-related code is going to be nothing compared to, say, waiting for a file to be read in, or a network request to come back, etc, so the cpu usage is typically not a concern. 2. You shouldn't have so many active async tasks at a time that causes the memory utilization of all of your promise objects to become a concern. 3. You generally shouldn't be using promises with synchronous work, so the fact that it forces a minimum wait time of one tick shouldn't be a concern.

---

<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 18, 2024, 9:42pm UTC](https://es.discourse.group/t/zero-overhead-async-await/2117/15 "2024-09-18T21:42:14Z")

</div>

@theScottyJam In some high performance servers, the speed of promises can show up on profiles sometimes, but I do generally agree with you in that promise resolution speed is almost never the real problem.

On a related note, I a while back ran some benchmark tests using raw TCP connections to a local server in Node. I found that the runtime itself limits you to around 2 Mbps single-threaded and 10 Mbps multi-threaded, with the bottleneck being the server. For comparison, on an optimized Rust+Tokio equivalent, I achieved 50 Gbps throughput. Promise overhead should be margin of error on Node.
