I was looking to get a bit more into async in nim with the C-backend and have a fair bit of experience with Javascript. There functionality such as setTimeout
is available to execute a function at a later time.
Example:
setTimeout(() => console.log("Potato"), 1000)
What does the equivalent in nim look like?
If you want a quick solution, just go with 1). If you're curious about the async fundamentals, read the entire post.
The simplest one is just using an async proc and sleepasync
:
import std/asyncdispatch
proc afterX(wait: int) {.async.} =
await sleepAsync(wait)
echo "Stuff Happened"
let future: Future[void] = afterX(500)
... You can do more stuff in between getting the future and waiting for it ...
waitFor future
The async
pragma changes the proc to return a Future
of whatever it was returning. In this case nothing, so void
. This future in your "global", synchronous context you can then just waitFor
.
Note that waitFor
is not similar to JS await
. JS has no equivalent to it as it does not have this level of access to the context it is executed in. waitFor
is for waiting for a task to complete outside of an async-context and it will block the thread to do so. Nim's await
is what functions the same as JS's await
and that can only be used within async procs.
Nim has a proc setTimer which you might stumble over if you search for timeout-related keywords in the docs.
That does what we want, but with very different, more low-level syntax, that functions almost the same as 1:
import std/asyncdispatch
let echoMe: Callback = proc(fd: AsyncFD): bool =
echo "Stuff Happened"
return true
addTimer(500, true, echoMe)
... You can do more stuff in between registering the callback and polling for it ...
poll()
AsyncFD
here is AsyncFileDescriptor
(or Handle on Windows). When doing async IO operations, this parameter is filled with the handle to whatever that IO operation is being done on. Not relevant for us in this scenario, but useful to know.
AddTimer "registers" the echoMe
proc on a the dispatcher (the sort of event-loop) to execute later.
poll
then fetches that proc and executes it.
As an aside and not relevant for the discussion:
The boolean used by addTimer
defines whether echoMe
should be removed from the dispatcher/event-loop after it was executed the first time. So false
means to execute it over and over again with time delays in between.
The boolean returned by echoMe
also defines whether it should deregistered from the dispatcher/event-loop, but does so after every execution. This only matters if addTimer
was given true
for its boolean in the first place. So true
from echoMe
means "Even if I am allowed to execute over and over, stop that now, deregister me from the event-loop".
When poll fetches a proc, it blocks the thread until it can get a task or until it times out. Which is eerily similar to waitFor. That is because waitFor is just a disguised call to poll
run in a while-loop until the future.finished
boolean flips from "false" to "true".
Both of these do what we want, albeit async does it with nicer syntax:
echoMe
) on a queue via a "task dispatcher" (The async
pragma / addTimer
) to execute later. The dispatcher functions as a sort-of event-loop.poll
, waitFor
) and execute it implicitlyThe main difference is that with the Async-Syntax, waiting for N milliseconds is part of the task itself (it is within echoMe
via sleepAsync
), while with addTimer
the waiting is done before the task is executed during polling.