Question

I'm trying to make (yet another) language that compiles to JavaScript. One of the features I'd like to have is the ability to perform JavaScript's async operations synchronously (not exactly synchronously – without blocking the main thread, of course). Less talk, more examples:

/* These two snippets should do exactly the same thing */

// the js way
var url = 'file.txt';
fetch(url)
    .then(function (data) {
        data = "(" + data + ")";
        return sendToServer(data);
    }).then(function (response) {
        console.log(response);
    });

// synchronous-like way
var url = 'file.txt';
var response = sendToServer("(" + fetch(url) + ")");
console.log(response);

What is the most elegant way to compile it back to JS?

The criteria, sorted by importance:

  1. Performance
  2. Backwards compatibility
  3. Compiled code readability (not really important)

There are several possible ways to implement it, those three came to my mind:

  1. Using Promises:

    • f(); a( b() ); would turn into
    • f().then(function(){ return b() }).then(function(x){ a(x) });
    • pros: relatively simple to implement, polyfillable back to ES3
    • cons: creating a new function and a Proxy instance for every function call
  2. Using generators:

    • f(); a( b() ); would turn into
    • yield f(); tmp = yield b(); yield a( tmp );
    • pros: nicer javascript
    • cons: creating proxies, generators and iterators on every step, also not polyfillable
    • EDIT: in fact they are polyfillable using another recompiler (eg. Regenerator)
  3. Using synchronous XMLHttpRequest:

    • request a server script which waits until another call from somewhere else in the code
    • pros: de facto no changes in the code, super-easy to implement
    • cons: requires server script (=>no portability, browser-only); furthermore, synchronous XMLHttpRequest blocks the thread so it does not work at all
    • EDIT: It actually would work inside a Worker

The main problem is that one can't tell if a function is asynchronous until runtime. That results in the two solutions that at least do work being a lot slower than pure JS. So I ask:

Are there any better ways to implement?

From answers:

Await keyword (@MI3Guy)

  • C# way (which is good!)
  • introduces a new keyword (which can be disguised as a function (1st-class-citizen) that eg. throws if the programmer tries to pass it as an argument)
  • How to know that function is asynchronous? No other keywords!
    • Every function that contains await keyword becomes asynchronous?
    • When the code gets to await, it returns a promise? Else treats as an ordinary function?
Était-ce utile?

La solution

Assuming the language is dynamically typed, I can see two different ways this could be handled without knowing at compile time if a call is async.

One approach would be to assume every call could be async. However, this would be incredibly inefficient and produce a lot of extra code. This is essentially what you are doing in option 2.

Another option is to use fibers. Fibers are like lightweight threads that are scheduled by the program. The fiber itself decides when to give up control. However, these require operating system support. As far as I can tell, the only way to use fibers in JavaScript is within node.js. My guess is that if you are compiling to JS that the goal (or at least a goal) is to run on the browser which rules out this option. This would be a more lightweight version of option 3.

Honestly, I would consider adding a keyword like C#'s await. This has a number of advantages in addition to specifying that a function is async. Functions can be called to produce futures without awaiting them immediately. This allows for multiple async operations to occur at once rather than in sequence. Additionally, it is better to be explicit about what operations are async because other code can run while the operation is taking place. If async is automatic, it may be surprising when some data you are using is modified by an external piece of code.

In C# within a method marked as async, the return statement is used to return the value that will be inside the future, not the future itself. Note, however, that the async modifier is not really part of the method signature. await can be used with any method that return a future.

If you don't wish to add an async modifier for functions, you could decide (as you stated) that any function with an await is treated like it has an implicit async modifier. So even if a function has an await, but returns before reaching it, the function should still return a future. I would also recommend exposing some way to convert a value into a completed future that contains the value, especially if there is no way to have that done implicitly by adding the async modifier.

Licencié sous: CC-BY-SA avec attribution
scroll top