Храни в asyncio.Queue очередь запросов к API. Отдельная задача пусть выбирает запросы из очереди, отправляет, получает ответ и оповещает о результате. Например, так.
import asyncio
import typing
class ThrottledResource:
def __init__(self, delay: float):
self._delay = delay
self._queue = asyncio.Queue()
self._task = None
def start(self):
self._task = asyncio.create_task(self._work_loop)
def stop(self):
self._task.cancel()
self._task = None
# этот метод вызывается клиентским кодом, получает параметры и возвращает отклик спустя время.
async def query(self, params):
future = asyncio.Future() # Future просигналит, когда наш запрос будет обслужен
await self._queue.put((future, params))
result = await future # корутина спит, пока запрос не обслужат
return result
async def _work_loop(self):
while True:
future, params = await. self._queue.get() # ждем, пока не придёт запрос
try:
result = await call_api(params) # тут делаем асинхронное обращение к сервису
except Exception as err:
future.set_exception(err) # была ошибка - теперь await future выкинет исключение
else:
future.set_result(result) # полуен результат - await future вернёт его
self._queue.task_done() # каждому успешному get() соответствует task_done()
asyncio.sleep(self._delay) # можно учесть, сколько времени делался запрос. Но стоит ли?
Код примерный, но идею передаёт. Использоваться будет как-то так
api = ThrottledResource(delay=1.0)
api.start()
...
result = await api.query(params) # await подождёт, пока не дойдёт очередь до нашего запроса
Нужно добавить обработку ошибок, корректное завершение работы при наличии задач в очереди, и так далее.
Технически вместо класса можно было реализовать это всё в виде декоратора над replier(), но это уже на вкус и цвет.