Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Asynchronous action feedback / chaining #96

Open
tusooa opened this issue Jan 21, 2021 · 6 comments
Open

Asynchronous action feedback / chaining #96

tusooa opened this issue Jan 21, 2021 · 6 comments

Comments

@tusooa
Copy link
Contributor

tusooa commented Jan 21, 2021

Assume we have some action that performs some async tasks:

ModelResult update(Model m, SomeAction)
{
    return { m, [](auto &&ctx) { 
        lager::get<SomeAsyncHandler>(ctx).someAsyncRequest()
            .then([=](auto res) { ctx.dispatch(UpdateModelAccordingly{res}); });
    } };
}

And we want to perform some other action after UpdateModelAccordingly has run. Here, using post-dispatch effect would not work as we would be processing according to the model, which it does not provide. The flow is roughly like:

SomeAction --| async request |~~~> UpdateModelAccordingly --> (some processing + SomeOtherAction{some data from model})

In traditional way it may be like

model.someAction()
    .then([](auto m, auto res) { m.updateModelAccordingly(res); return m; })
    .then([](auto m) { someProcessing(); m.someOtherAction(some data from model); return m; });

Although it is possible to use wrapper actions for that, but it is cumbersome when the flow gets longer (3-4 requests).
Another solution may be to use lambdas as actions, but then they are no longer good value types.

Is there a better solution for this? Thank you.

@arximboldi
Copy link
Owner

Hi!

I do not understand why the post-dispatch effect or even the current API don't work.

Can't you do?

ModelResult update(Model m, SomeAction)
{
    return { m, [](auto &&ctx) { 
        lager::get<SomeAsyncHandler>(ctx).someAsyncRequest()
            .then([=](auto res) { ctx.dispatch(UpdateModelAccordingly{res}); });
    } };
}

ModelResult update(Model m, UpdateModelAccordingly)
{
    // update model...
    return { m, [m](auto &&ctx) {  // <-- capture here the model or parts you need
         // more processing
         ctx.dispatch(SomeOtherAction{<some data from model>})
    } };
}

@arximboldi
Copy link
Owner

I think it would be fair though to consider changing the API such that you know the resulting model after an action has been delivered:

store.dispatch(some_action{})
    .then([](auto ctx, auto model) {
         // more effects depending on action having been deliverd 
    });

The problem is that while we can implement that for store, this can not be done in general for a context because it is agnostic of the underlying model type. Maybe you can workaround by providing a reader as part of the deps provided by the context.

@arximboldi
Copy link
Owner

I would like to understand a bit better the use-case. In general I am a bit wary of adding too powerful API's in effect land, because with too much power comes 🍝 code 😂

@tusooa
Copy link
Contributor Author

tusooa commented Jan 22, 2021

I would like to understand a bit better the use-case. In general I am a bit wary of adding too powerful API's in effect land, because with too much power comes spaghetti code joy

To pinpoint a specific use case I would say the flow of sending an encrypted message in matrix, as follows:

Get all members in a room, if needed -> {async receive response} -> Get [public] keys for every device of every member -> {async recceive response} -> Send the actual message encrypted with megolm[a symmetric cipher] and (at the same time) Send the symmetric encryption key to every device, encrypted by their public keys

I am now using a hacky way to implement this flow -> https://gitlab.com/kazv/libkazv/-/blob/af7a7fef79ef4a58485816c2561aa0ab51798a51/src/client/sdk-model.cpp .

Comparing to what matrix-nio does:
https://github.com/poljar/matrix-nio/blob/f4ffda2f7e2ffc618bdcae690e7cdff48f76fb99/nio/client/async_client.py#L1469

The reason I do not want ctx.dispatch(SomeOtherAction{<some data from model>}) to happen in the effects of the reducer for UpdateModelAccordingly is that I would like it to be reused, so that it may be used in other flows, for example, we will want to fetch the list of room members not only because we want to send an encrypted message; it could also be that we want to tab-complete usernames when the user is typing.

Thank you again for your help.

@tusooa
Copy link
Contributor Author

tusooa commented Jan 31, 2021

Kind of solving it by making another type of store.

https://gitlab.com/kazv/libkazv/-/blob/servant/src/tests/store-test.cpp

@arximboldi
Copy link
Owner

That's super cool. Hopefully I can take a look at it more deeply soon... maybe we can reconcile the differences and integrate something like that in Lager!

jacktony980 pushed a commit to jacktony980/sendingcpp that referenced this issue May 15, 2023
We now obtain the member list of the room if
the members are not fully loaded yet, and
query for their identity keys. Thus we
can send them encrypted messages without manually
refreshing member list. Although in a hacky way.

See also arximboldi/lager#96
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Projects
None yet
Development

No branches or pull requests

2 participants