Querying stuff from relays is not trivial. I am currently using 8 separate layers of abstraction to build feeds:
1. A websocket wrapper
2. A message buffer to avoid locking up the ui, sending too many concurrent reqs, and handle pausing messages while negotiating auth
3. A target interface which allows for different relay implementations like multiplextr or a local relay
4. An executor that works with a target to actually do stuff
5. A subscription object which queries multiple relays, aggregates results, and tracks which relay an event was seen on
6. A cursor object to perform windowing for a given relay
7. A load function which throttles and merges concurrent filters with a timeout
8. Feed/context loaders that take a filter, listen with a subscription, load using cursors, and fetch context with loaders
If relays always returned results in order it would simplify and optimize some of this away. But it really is irreducably complex.