fut icon indicating copy to clipboard operation
fut copied to clipboard

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

Open caesay opened this issue 1 year ago • 3 comments

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.

  • C: I'm not sure, one option would be limiting support to Task but not Task<T>, and returning some kind of wait handle?
  • C++: std::future
  • C#: Task<T>
  • D: There is an implement of JS promise here: https://github.com/CyberShadow/ae/tree/next/utils/promise
  • JS/TS: Promise<T>
  • Java: CompletableFuture<T>
  • Swift: Possibly want to use a promise library like: https://github.com/khanlou/Promise (quite light weight)
  • Python: Probably the most comparable thing is a asyncio.Future available in >=3.5 (https://docs.python.org/dev/library/asyncio-task.html#asyncio.Future)

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();
}

caesay avatar Feb 20 '24 19:02 caesay

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.

pfusik avatar Feb 20 '24 22:02 pfusik

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.

caesay avatar Feb 21 '24 09:02 caesay

Thank you!

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

pfusik avatar Feb 21 '24 10:02 pfusik