fusionlanguage / fut

Fusion programming language. Transpiling to C, C++, C#, D, Java, JavaScript, Python, Swift, TypeScript and OpenCL C.
https://fusion-lang.org
GNU General Public License v3.0
1.76k stars 55 forks source link

Promise / Callback Types (Instead of async/await?) #146

Open caesay opened 9 months ago

caesay commented 9 months ago

I think this depends on #144.

It's a very common library paradigm to provide asynchronous functions. In JS, it's not even possible to wait synchronously on a promise, so if directly using an asynchronous API in a native { } block, this can not be propagated up to a consumer of the library. It's also not possible to start a new thread to run a synchronous function in the background - so it's particularly important in that environment.

I suggest a simple implementation (when compared with an async/await state machine).

Also I'll note, #144 is a good step in the right direction, but falls short because it doesn't allow the consumer to easily block synchronously on the result.

The first step would be allowing the Task<T> type to be returned from methods. Translated into language-specific "Promise" types.

The second step would be some way to create and complete tasks:

class Task<T>
{
    public void Then(Action<T> fn);    
    public void Error(Action<TErr> fn);
}

class TaskFactory<T>
{
    public Task<T> AsTask();
    public void SetResult(T result);
    public void SetCancelled();
    public void SetException(Exception e);
}

The proposed API would allow Fusion authors to fully utilise and expose native asynchronous functionality initially, and later on, presumably, the Fusion standard lib will also expose some asynchronous functions.

Some examples:

public Task<string> GetStringFromUrl(string url)
{
    Task<string> ret; // this is required or fusion thinks the method doesn't return.
    native
    {
        var http = new System.Net.Http.HttpClient();
        ret = http.GetStringAsync(url);
    }
    return ret;
}

public Task<string> GetProcessOutputAsync(string path)
{
    TaskFactory<string> source;
    native
    {
        var psi = new System.Diagnostics.ProcessStartInfo()
        {
            CreateNoWindow = true,
            FileName = path,
            RedirectStandardError = true,
            RedirectStandardOutput = true,
            UseShellExecute = false,
        };

        System.Text.StringBuilder output = new System.Text.StringBuilder();

        var process = new System.Diagnostics.Process();
        process.StartInfo = psi;
        process.ErrorDataReceived += (sender, e) =>
        {
            if (e.Data != null) output.AppendLine(e.Data);
        };
        process.OutputDataReceived += (sender, e) =>
        {
            if (e.Data != null) output.AppendLine(e.Data);
        };

        process.Start();
        process.BeginErrorReadLine();
        process.BeginOutputReadLine();
        process.WaitForExit();

        process.WaitForExitAsync().ContinueWith(t =>
        {
            if (t.IsFaulted) source.SetException(t.Exception);
            else if (process.ExitCode != 0) source.SetException(new System.Exception($"Process exited with code {process.ExitCode}"));
            else source.SetResult(output.ToString());
        });
    }
    return source.AsTask();
}
pfusik commented 9 months ago

I have very limited experience writing async code. I know Node.js heavily relies on it, but is it also used in browser JS ?

It took C# a decade to incorporate it and Java still doesn't have async/await. Your code not only shows delegates, but also lambda functions. The problem with the latter is that C still doesn't support them.

caesay commented 9 months ago

I'm not suggesting we do async/await. I'm suggesting we support asynchronous callbacks (eg. Promise, Future, Task) - which predate async/await style keywords significantly. Java does have similar concepts (CompletableFuture) and C++ too (std::future)

In JS/TS, yes of course - async callbacks is even more important in the browser. Asynchronous code in the browser dates back to the very beginning of javascript. Pretty much everything you did had to be interrupted and continued at some point later with a callback (think waiting for user input, waiting for an HTTP request to complete, waiting for an animation to play, etc etc etc). Promise was implemented at some point later as an abstraction over the top of pure callbacks to simplify returning success/failure statuses and also chaining promises together (.then() and .error() is similar C# .ContinueWith()). It is not possible to use JS today without significantly relying on callbacks and promises, because many (especially browser functions) only provide asynchronous API's - anything synchronous would block the user input and freeze up the page since JS is single threaded. There is no way to synchronously wait on an asynchronous function for this reason too - to avoid deadlocks and UI freezes. There are some synchronous functions offered in the nodejs context, however using them is not advisable for the same reason, imagine you're writing a HTTP server, but the moment you do a file copy you stop handling requests because you're synchronously blocking your only thread doing some simple IO....

In other languages, it's probably not quite as important (in JS it's an absolute requirement as a library author). However every language will have a similar concept to a Promise, and if it doesn't, a simple abstraction can be added on top of pure callbacks, which is why I suggest that this depends on #144.

The reasons above are also why when we start writing API's like "CopyFile", "HttpRequest" and so forth, we need to support and expose asynchronous functions in Fusion... otherwise it will never be possible to use Fusion as a library for JS in a practical sense because of it's singlethreaded nature.

pfusik commented 9 months ago

Thank you!

Last time I did any significant browser JS was a dozen years ago: validation, DOM manipulations and AJAX. No Promises. :)