I am currently stuck creating a module that uses the Redis module asynchronously. I don't really know how to solve this so maybe you could help me. Here is what I imagined to do:
import redis, asyncdispatch
# I need this proc to instantiate "db" and other stuff that uses "await"
proc instantiate {.async, discardable.} =
const
port = 6379.Port
domain = "127.0.0.1"
let
db = await openAsync(domain, port)
var
keystring: string = ""
# I need this proc to be within the instantiate proc, so it can use the "db" var, since it is not available outside the instantiate proc
proc rsearch(pattern: string) {.async, discardable.} =
keystring = repr(await db.keys(pattern))
keystring.echo
# Here is where I am stuck
# In theory, this should be callable from the .nim that imported this module
rsearch("test")
# But the proc within the proc is only callable from within the initial instantiate proc, which is useless
# I tried different ways of establishing what I wish for but failed
# Above code is the result of the last thing I tried
Probably easy to solve for you, but I couldn't wrap my head around this.
You need to call your async proc with waitFor
e.g https://github.com/xmonader/nim-redisclient/blob/master/tests/testredisclient.nim
You should put your data into an object and then pass it around your procs. Don't create "global" state and capture it in closures.
Here is what I mean (probably doesn't compile, but I hope you get the idea):
import redis, asyncdispatch
type
MyState = ref object
db: AsyncRedis
keystring: string
proc newMyState(): MyState {.async, discardable.} =
const
port = 6379.Port
domain = "127.0.0.1"
return MyState(
db: await openAsync(domain, port)
)
proc rsearch(self: MyState, pattern: string) {.async, discardable.} =
self.keystring = repr(await db.keys(pattern))
self.keystring.echo
let state = newMyState()
waitFor state.rsearch("test")
Whoa. That's a surprising reaction. I think you might be missing something significant about how async works to think that.
Sadly, your example does not work for me, even with tweaks, as the most important part proc newMyState cannot work, as it couldn't be able to return anything else aside from Future[T] or Future[void]
Why is this a problem? The procedure does async operations, so it must return a future. You can hack around and not do that, but then your procedure becomes blocking.
What is even worse: even if that would work it would kind of defeat the purpose of wrapping the procedures in the first place, as I wanted to simplify them first.
Perhaps it would help if you explained what you are doing at a high level in some more detail. What are you trying to simplify? Are you writing a library on top of redis that makes certain operations simpler?
If I have to do something like waitFor state.rsearch("test") in the final code, anyway, then there is no point in wrapping everything.
Again, that procedure performs async operations, so it must return a future. In a non-async context you have two choices for what to do with a future:
If you were using this rsearch proc inside of another async proc then you'd be able to await it (or also run it in the background via asyncCheck).
Please don't give up on async. Making your code synchronous will not get you far.
Why is this a problem? The procedure does async operations, so it must return a future. You can hack around and not do that, but then your procedure becomes blocking.
It is a problem because I don't know how to work around that effectively and use these procs as I intend to, i.e. use them in a simplified manner.
Perhaps it would help if you explained what you are doing at a high level in some more detail. What are you trying to simplify? Are you writing a library on top of redis that makes certain operations simpler?
Yes. I don't want any hassle in the head nim file with accessing the database correctly, so I want to wrap the procedures to make access very easy for me.
Again, that procedure performs async operations, so it must return a future.
Yes, that is the problem. How do I make the procs simpler if they always must return a Future (instead of a simple string, e.g.)? It is also needed to wrap every await'ed in an async procedure. So everything must be an async proc with its async properties.
Please don't give up on async. Making your code synchronous will not get you far.
I sat several hours at a couple of lines of code, because I couldn't wrap my head around using async access to the database the way I imagine to, i.e. simplified and safe in the main file.
Yesterday, I started rewriting the module synced and I am almost finished with it. It was a piece of cake. It also lets me implement the procs the way I imagine to; i.e. very simplified and doing what I want.
That said, I also have contemplated and read more about async programming, etc. and came to the conclusion that my very simple app to be won't really need async access, as it is intended to be used by 1 person for very simple operations, like tagging an entry with keywords. That's basically the gist of the minimally viable product I am trying to create.
Probably, I am not familiar enough with this language yet, to achieve some advanced simplification techniques with async procs, but right now in this situation I don't seem to need it. First, I want the app to run properly the way I want. If it will be expanded and improved, I may upgrade database access to be asynchronous, as soon as I will have gained enough knowledge on how to solve the application of the latter the way I imagine.
Anyway, thanks for the dedicated help, I appreciate it.
I don't think it's an issue with Nim then but more with async programming.
Nim follows the semantics of C#, Python and Javascript async/await, and it also uses the type system to ensure there is no logical mistake in the code, like using a potentially empty async result in part of the code that cannot handle it (i.e. sync code).
Yes async code is viral, but introducing those boundaries between sync and async ensures that it is logically safe and you don't read garbage (future results not yet arrived) in your sync code. The added benefits is that within async code you can write in the same style as sync code.
The alternative to async/await would be similar to Javascript Promises and the type would be worse you would have proc callbacks instead of Future.