-
Notifications
You must be signed in to change notification settings - Fork 27
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Async/await design proposal #37
Comments
Proposed designNote: There is a babel plugin to transform async to promises. I considered using that because it's an off-the-shelf way of supporting async with very little development cost on my end, but when I ran it on some test code, it looks like the output is horrendously complicated and verbose, especially in the case of things like loops. I don't think I could ever stomach using async if it were that inefficient. CPSThe core of this design is based on continuation-passing style (CPS), where an awaiting caller passes in a callback that is a continuation to execute when the async callee completes. This is fundamentally different from typical promises where the caller subscribes a callback after the callee returns. I believe that the continuation-passing style is much more memory efficient, but it will only work in a subset of scenarios (more on that later) For example, if async function I'm proposing the following memory structure for such a scenario: In this design, an idle (awaiting) async function consumes 8 bytes of memory (3 slots + a 2-byte allocation header), plus 2 bytes for each variable in the async function.
To do this, I propose a modification to the current 6-byte closure structure. Currently, a closure is a 6-byte tuple that pairs a function pointer with an environment pointer. In this proposed change, a closure is also its own environment, rather than holding a pointer to its environment. It still maintains the
When a closure is called, the function in the second slot (in the diagram this is the AwaiterIn this proposed async design, a call like awaiter(isSuccess, result) The
In the case of one Compatibility with promisesThe above proposed CPS "core" will only work in the specific scenario where one async function is calling and awaiting another async function, since both the caller and callee are under the control of the compiler. I believe this to be the most common case, and something worth optimizing for. But we need to also support the case where we await things that are not other async functions, and similarly need to support the use of calling an async function but not awaiting it (or at least not immediately). The compiler will recognize the syntactic form This is the ideal/optimal case, but there are other cases to deal with:
In case In case Case In case I've elided a lot of the details here, because I haven't worked through them yet. I'm thinking that there will be probably be a new virtual register named What does
|
Version 8.0.0 brings async-await. I'll be doing some write-ups on how it works. It's roughly what I proposed above but slightly more efficient, only requiring 6 bytes of idle memory in the best case. |
Async/await in Microvium
I'm not "promising" that async/await will be implemented in Microvium, but it's on my radar. This GitHub ticket is a consolidation of my personal design notes on the feature, in case anyone wants to review it and offer suggestions or improvements, or just for those interested in the process. Feel free to add comments to this ticket with any questions, corrections, clarifications or suggestions.
In this first message, I'll cover the motivation and requirements, and then in the next message, I'll cover the proposed design.
Why async/await?
Objectives
async
functions thatawait
the result of otherasync
functions.async
function canawait
that isn't anotherasync
function. E.g. a thenable or promise. Ideally, this should be the syntaxnew Promise(...)
since this is the typical way to do this in JS.Gotchas to watch out for
What should the following print (in particular, in what order)?
The answer is:
The key gotcha here is that the
thenable
completes last. Why? Because a thenable is not a realPromise
, so the ECMA-262 spec says that it must be wrapped in a promise. I think the extra wrapping layer causes it to be posted to the promise job queue twice, so the callback is run last.This also means that thenables are necessarily less efficient than promises, because thenables must be wrapped in promises anyway (so you land up having both the thenable and the promise in RAM) and they must have this behavior where they land up on the promise job queue twice instead of once.
Here's another gotcha:
This prints:
The key thing here is that the thenable
c
completes first. Why? Because Promises always evaluate their callback asynchronously in the promise job queue, while an arbitrary thenable does not.As stated in the objectives, if this code runs in Microvium then it must produce the same output. We have the choice either to forbid/remove certain features or to implement them correctly.
The text was updated successfully, but these errors were encountered: