wailsapp / wails

Create beautiful applications using Go
https://wails.io
MIT License
25.39k stars 1.23k forks source link

Is it possible to receive async results from the backend? #1345

Closed benjamin-thomas closed 2 years ago

benjamin-thomas commented 2 years ago

Is your feature request related to a problem? Please describe.

I would like to call a function from the frontend, but without knowing its total execution time. That function could take a few seconds to complete, but I could also have some data available after a few milliseconds.

I would like the UI to react as soon as some data is available for a nicer user experience.

So I've been thinking how to implement a pub/sub pattern, or some kind of emitter from the backend.

Have you thought about this problem at all?

Describe the solution you'd like

From the backend perspective, I would like to write something like:

func (a *App) ProduceAsync(messages chan<- string) {
    for _, m := range []string{"A", "B", "C"} {
        messages <- m
        time.Sleep(1 * time.Second)
    }
    close(messages)
}

But I don't see how the frontend would be able to call such a method at the moment?

From the frontend perpective, I would like to write something like:

document.getElementById('my-btn').addEventListener('click', () => {
            window.go.main.App.ProduceOnce().then(val => {
                console.log("Got one value from the server")
            });

            window.go.main.App.ProduceAsync().subscribe(val => {
                console.log("Got one value from the server, but I may get more...")
                // then somebody has to deal with releasing some memory somewhere...
            });
        });

But I failed to find some code constructs that would allow the 2 to communicate in an async fashion.

Calling runtime.EventsOn from the backend and frontend works though but it'd be nice if we could isolate emitted events/messages close to the method call to prevent jQuery style event soup :)

Describe alternatives you've considered

I tried to play with the idea of returning a custom type, but its public functions are not available to the frontend so I'm pretty limited.

type Emitter struct {
    Name string // available to the frontend OK
}

func (e Emitter) String() string { // this method is not available to the frontend
    return "MyEmitter"
}

Additional context

Not a request, but just a vague idea. Unless I'm mistaken, reactive extensions are available in many languages and could provide the common ground for the backend/frontend async operations.

So maybe integrating parts of those libraries could be a nice addition to wails?

leaanthony commented 2 years ago

Thanks for opening this. You can bind any instance of a struct to the frontend using the standard bind method. You should probably run your emitter in a goroutine. Listening to the event using EventsOn should then tie it together.

giovapanasiti commented 2 years ago

@leaanthony can you give us an example? So far I'm trying to use websocket to achieve the result but it implies to have an http server so if you have another solution that would be great!

benjamin-thomas commented 2 years ago

@giovapanasiti I intend to implement and point to a little demo in the next few days to talk about a solution or a recommendation to some kind of pattern because all is not clear in my head at the moment :)

benjamin-thomas commented 2 years ago

Hello,

Could you have a look at this repo ? https://github.com/benjamin-thomas/wails-async-demo2

I tried to play around with the idea of implementing an emitter, but got lost on the way :)

Ideally I think I would want to consume the output of a channel, or something similar to it. But that's not doable.

Is this the only way to have async communication with the backend?

Thanks

leaanthony commented 2 years ago

Yes, that's the standard way of doing it. All the comms between frontend and backend is async anyway, as I'm sure you're aware. I did think a JS equivalent of a channel would be cool but I'm not sure what the use case really is right now.