espruino / Espruino

The Espruino JavaScript interpreter - Official Repo
http://www.espruino.com/
Other
2.76k stars 742 forks source link

 async/await support #1272

Open opichals opened 6 years ago

opichals commented 6 years ago

As there is Promise support. How difficult would be to add this as well?

A brief explanation is available at http://2ality.com/2016/02/async-functions.html.

The spec (ES2017) is available at https://tc39.github.io/ecma262/#sec-async-function-objects.

gfwilliams commented 6 years ago

I had wondered about this - but it's actually pretty difficult to do because of the way Espruino works... It's got to be able to completely stop execution and back out of the execution stack, and then resume from where it left off.

vshymanskyy commented 6 years ago

babel should be able to translate async/await into normal js code, just need to find proper configuration for Espruino...

vshymanskyy commented 6 years ago

Ok, I was actually able to get async/await working on Espruino (using Babel transpiler and some hacking). Here is a full example: https://github.com/vshymanskyy/espruino-await Looks pretty usable, but a native implementation would be so much better ;) What do you think?

opichals commented 6 years ago

but it's actually pretty difficult to do because of the way Espruino works...

I haven't really looked into it but my naive thinking would lead me to try to do 'live' regenerator-like stuff in the interpreter. This is just a basic idea so please bare with me here :)

Whenever the code is using the await keyword Espruino would live-transpile the lines so it would work as if it was replaced with Promise use. For example for

const makeRequest = async () => {
  await callAPromise()
  throw new Error("oops");
}

It would make it internally interpret something like the following

const makeRequest = () => {
  return callAPromise()
    .then(() => {
      throw new Error("oops");
    })
}
gfwilliams commented 6 years ago

If it were just in a straight bit of code it'd be pretty easy, but it's how you deal with await in things like FOR and IF that's the problem :(

opichals commented 6 years ago

await in things like FOR and IF that's the problem

@gfwilliams Again, I haven't found anything specific... This http://blog.ministryofprogramming.com/async-await-and-conditional-promises/ doesn't spark any light for me as for why it is a problem. Perhaps there are some Espruino implementation details that make it obvious... could you please point me to some code to study more stuff to 'get it'? Thanks! :)

gfwilliams commented 6 years ago

Compile for linux, then run some code in Espruino in GDB and step through execution. The issue is that Espruino doesn't use bytecode like a 'normal' interpreter. Quite a bit of state is stored in the main execution stack - so when the interpreter stops execution that would all disappear.

You'd have to come up with a way of skipping to the correct point in the code, but also you'd have to modify the parsing of every loop/if/switch/etc such that it could stop execution, save its current state to a JsVar and then restart. It'd be a nightmare.

It's such an upheaval that probably the most sane solution is to actually rewrite the interpeter to use bytecode (well, this) instead, then you'd get a huge performance boost too. We could create a second parser that just handled a subset of JS, then it'd run through and would compile if it could - otherwise we'd fall back to the old parser.

idobh2 commented 1 year ago

If anyone ever reaches here :) After the 2 fixes @gfwilliams just introduced -https://github.com/espruino/Espruino/commit/c512aa57591fb0270d2809c2012df11c25971e72 & https://github.com/espruino/Espruino/commit/bdadb3cdfec72ad44d422d689fa1a399c596faaf - async/await are transpiling great to ES5, and working well. I'm using @swc/core to to it, with the following sequence:

import { minify, bundle, transform } from "@swc/core";
import path from "path";

// bundle
const { ["index.ts"]: { code: bundled } } = await bundle({
    entry: path.resolve(__dirname, "src/index.ts"),
});

// transpile to es5
const { code: transformed } = await transform(bundled, {
    jsc: {
        target: "es5", // this is the actual magic
    }
});

// minify
const { code } = await minify(transformed, {
    compress: true,
});

// code is what you push to the espruino device

I'm pretty sure tsc will result in similar working code as well, but haven't tested it