influxdata / influxdb-client-js

InfluxDB 2.0 JavaScript client

Home Page:https://influxdata.github.io/influxdb-client-js/

Geek Repo:Geek Repo

Github PK Tool:Github PK Tool

Ability to query rows using async iterables

greim opened this issue · comments

Proposal:
A QueryApi#iterateRows() method which allows you to asynchronously iterate over rows using built-in JavaScript language features, and to automatically trigger cancellation, backpressure, and error handling based on my program's behavior, instead of having to explicitly opt into it.

Current behavior:
The QueryApi#queryRows() method has a few drawbacks. Callbacks unnecessarily compartmentalize your logic and take you out of the flow of your program, including any try/catch blocks. Plus, there's no obvious way to do backpressure. Finally, I discovered useCancellable by perusing this lib's .d.ts file, but I'm not sure from reading docs how to use it.

queryApi.queryRows(query, {
  next: (row, tableMeta) => {
    // Do something with `row` and `tableMeta`...
  },
  error: (error) => {
    // Handle errors here
  },
  complete: () => {
    // Done
  },
  useCancellable: (cancelable) => {
    // How does this work? Maybe this?
    if (condition) {
      cancelable.cancel();
    }
  },
});

Desired behavior:
I'd like to be able to do this.

for await (const { row, tableMeta } of queryApi.iterateRows(query)) {
  // Do something with `row` and `tableMeta`...
  // Any `break`, `throw`, or `return` in here triggers cancellation
  // Any `throw` in here propagates to nearest try/catch
  // Any `await` in here creates backpressure
}
// Done

This lib can universally implement cancellation, backpressure, and error handling on behalf of users thusly:

  • Cancellation: Implement or override the returned async iterable's .return() method to perform any cleanup or abort.
  • Backpressure: Depends on how this lib consumes source data. If consuming a Node.js source stream for example, it could throttle it based on whether the client has finished processing the latest row.
  • Error handling: Simply return a rejected promise from iterator.next().

Alternatives considered:
Can continue using the existing callback API.

Use case:
Allows me to consume rows using idiomatic, built-in JavaScript language features, and to compose this lib more naturally with other promise-based and async-iterator-based utilities, and to avoid eating memory if my code can't consume data as fast as this lib produces it.

Thank you @greim for posting an issue. The callback style is meant to be a low-level functionality, it is used through the examples because it can consume bigger datasets without any extra dependencies/requirements.

RxJS is currently supported as a nicer alternative. Async iterators are mature enough these days that they can be added. Still, iterator is an experimental feature in node.js HTTP responses.

Thanks for the response.

Still, iterator is an experimental feature in node.js HTTP responses.

Yeah, it would definitely make sense to model it as a transformation of iterables: AsyncIterable<StreamChunk> => AsyncIterable<Row>, but if instability is a concern it could also be achieved by writing an adapter around the classic (stable) streams API and just manually implement the async iterable protocol.

Maintainability and orthogonal support in both node.js and browser/deno is a concern to me. First, I plan to change the existing low-level API so that response data flow can be paused and resumed. Later on, adapters can be added to provide async iterables. Yes, I have to think it over, there are more implementation approaches.

@greim #592 delivers the requested improvements, I plan to merge the PR at the end of this week, you can take a look and comment if you want to.