We can create template with like this:
template withFile(name: string, body: untyped) =
   let fh = open(name, ...)
   defer: close(fh)
   block:
      body
template withDb(body: untyped) =
   let db = await open(...)
   defer: close(db)
   block:
      body
Because a template cannot be async and the 'await' is not removed by macro async, so the compiler will find undeclared 'await', but is there any way to make the code above works? Thanks.
Yes, you can do this:
template withDb(body: untyped) =
   let dbFut = open(...)
   yield dbFut
   let db = dbFut.read()
   defer: close(db)
   block:
      body
Nim's 'async' macro rewrites the code so that it becomes iterable/etc where needed.
https://nim-lang.org/docs/asyncdispatch.html#17
I think what Dom's doing here is giving a slightly desugared version of what the 'async' macro would normally do for your code.
@dom96 Thanks. The code seems works, but it doesn't. db.close() seems executed before the code 'block', this is the error message:
asyncdispatch.nim(1492)  waitFor
asyncdispatch.nim(1496)  poll
asyncdispatch.nim(292)   runOnce
Error: unhandled exception: No handles or timers registered in dispatcher. [ValueError]
So I must use the following code:
template withDb(body: untyped) =
   let dbFut = open(...)
   yield dbFut
   let db = dbFut.read()
   block:
      body
   close(db)
But the code above cannot deal with the exception raised from 'body', the code 'body' raise a exception, close(db) will not be executed. I cannot use try...finally either, the compiler will not pass.
So there is a really big problem, how to close the resource reliablely within a async proc?
You need to handle the exception: https://nim-lang.org/docs/asyncdispatch.html#asynchronous-procedures-handling-exceptions
(read will raise the exception if one exists, so you should do this before calling it)
The following code is extracted from asyncpg.
template withConnection*(pool: apgPool, conn, body: untyped) =
  ## Retrieves first available connection from pool, assign it
  ## to variable with name `conn`. You can use this connection
  ## inside withConnection code block.
  mixin getFreeConnection
  var connFuture = getFreeConnection(pool)
  yield connFuture
  var index = connFuture.read
  block:
    var conn = pool.connections[index]
    body
  pool.futures[index].complete()
When code in 'body' raise a exception, the last line "pool.future[index].complete()" will not be executed and the connection in the pool will be leaked. Wrapper the body in a proc is not possible when body contains async call itself. I have no idea how to make the above code works right.
@dom96 is right, it can be done like this:
macro withTran(db, body: untyped): untyped =
   quote do:
      block:
         proc action(`db`: apgPoolConnection) {.async.} =
            `body`
         let fut = doTransaction(action)
         yield fut
         if fut.failed: raise fut.readError