Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Promises based transform() #185

Closed
tyoshino opened this issue Aug 28, 2014 · 6 comments
Closed

Promises based transform() #185

tyoshino opened this issue Aug 28, 2014 · 6 comments

Comments

@tyoshino
Copy link
Member

Branched from https://github.com/whatwg/streams/pull/180/files#r16783645

tyoshino

Do we want to also change transform() to use Promises to notify completion?

domenic

No, because transform() is more like ReadableStream: you can enqueue multiple chunks before calling done().

Although in theory, for both ReadableStream and for Transform, we could use a combination of enqueue + promises. I think it would be more awkward though? Unsure, might write up some sample code.

@calvinmetcalf
Copy link
Contributor

there is precedence with node.js streams where values can either be passes to push (enqueue) or to the callback, in practice this works pretty well for the large percentage of the time where you are only transforming one thing.

@domenic
Copy link
Member

domenic commented Sep 3, 2014

@calvinmetcalf
Copy link
Contributor

That is how it works, though it isn't actually documented, (writing pull request for the documentation now)

@calvinmetcalf
Copy link
Contributor

@domenic
Copy link
Member

domenic commented Jan 24, 2015

Consider the example at https://streams.spec.whatwg.org/#example-rs-pull, in particular:

    pull(enqueue, close, error) {
      const buffer = new ArrayBuffer(CHUNK_SIZE);

      fs.read(fd, buffer, 0, CHUNK_SIZE, position).then(bytesRead => {
        if (bytesRead === 0) {
          return fs.close(fd).then(close);
        } else {
          position += bytesRead;
          enqueue(buffer);
        }
      })
      .catch(error);
    },

with promises, this would become

    pull(enqueue, close) {
      const buffer = new ArrayBuffer(CHUNK_SIZE);

      return fs.read(fd, buffer, 0, CHUNK_SIZE, position).then(bytesRead => {
        if (bytesRead === 0) {
          return fs.close(fd).then(close);
        } else {
          position += bytesRead;
          enqueue(buffer);
        }
      });
    },

That is, it allows you to return a promise for error propagation. That's kind of nice. Not revolutionary. And I think it introduces a one-turn delay?

However, consider the case of a simple duplicater transform:

transform(chunk, enqueue, done, close, error) {
  enqueue(chunk);
  enqueue(chunk);
  done();
}

could become

transform(chunk, enqueue, close) {
  enqueue(chunk);
  enqueue(chunk);
}

which is a lot nicer. (But this time you definitely get a one-turn delay before we can call transform again.) Async transforms too:

transform(chunk, enqueue, done, close, error) {
  getAsyncData(chunk).then(d => { enqueue(d); done(); }, error);
}

becomes

transform(chunk, enqueue, close) {
  return getAsyncData(chunk).then(enqueue);
}

One thing that has me confused is that the current transform implementation doesn't pass close and error params like I do above. Perhaps because we expect the creator to just call transform.writable.close() and transform.writable.abort(er)? That seems lame. Maybe it was just an oversight.


I am actually somewhat convinced now that transform is more like writable than readable, contradicting my position from the OP. So it seems pretty clear that we want to be able to return promises from transform.

However, I'm not as sure about whether we want to use promises for readable-stream pull. I am leaning toward yes, since I think as long as we still use enqueue for enqueuing data, there is no extra one-turn delay. I need to analyze the algorithms a bit more to be sure.

This is still in my sprint for this week; hopefully I can figure out the modification to ReadableStream at least this weekend since we want to nail down ReadableStream ASAP.

domenic added a commit that referenced this issue Jan 27, 2015
See discussion in #185 for background.

This allows more convenient integration with promise-returning underlying source functions, removing the need for `.catch(error)` as seen in the modified example.

It also rationalizes the slightly-strange way in which pull() is mutexed based on whether enqueue() has been called: instead, it is now mutexed so as to not be called again until the previous iteration's promise has fulfilled. This gives more direct control to the underlying source, and allows them to deal more easily with cases where there are just no chunks to enqueue.

Although superficially it seems this may reduce performance for cases where underlying sources can enqueue many chunks synchronously, this is not actually the case, as can be seen from the modified test illustrating such a scenario. If an underlying source can enqueue multiple chunks synchronously, then it should just do so! It shouldn't wait to be pull()ed, as the previous test was doing.
@domenic
Copy link
Member

domenic commented Jan 27, 2015

#272 fixes this for readable streams. I will leave this open to track updating transform streams for a future sprint when I focus on those, but remove the "Week of 2015-01-19" milestone since the portion I wanted to accomplish last week is now finished.

@domenic domenic removed this from the Week of 2015-01-19 milestone Jan 27, 2015
domenic added a commit that referenced this issue Jan 27, 2015
See discussion in #185 for background.

This allows more convenient integration with promise-returning underlying source functions, removing the need for `.catch(error)` as seen in the modified example.

It also rationalizes the slightly-strange way in which pull() is mutexed based on whether enqueue() has been called: instead, it is now mutexed so as to not be called again until the previous iteration's promise has fulfilled. This gives more direct control to the underlying source, and allows them to deal more easily with cases where there are just no chunks to enqueue.

Although superficially it seems this may reduce performance for cases where underlying sources can enqueue many chunks synchronously, this is not actually the case, as can be seen from the modified test illustrating such a scenario. If an underlying source can enqueue multiple chunks synchronously, then it should just do so! It shouldn't wait to be pull()ed, as the previous test was doing.
domenic added a commit that referenced this issue Feb 2, 2015
See discussion in #185 for background.

This allows more convenient integration with promise-returning underlying source functions, removing the need for `.catch(error)` as seen in the modified example.

It also rationalizes the slightly-strange way in which pull() is mutexed based on whether enqueue() has been called: instead, it is now mutexed so as to not be called again until the previous iteration's promise has fulfilled. This gives more direct control to the underlying source, and allows them to deal more easily with cases where there are just no chunks to enqueue.

Although superficially it seems this may reduce performance for cases where underlying sources can enqueue many chunks synchronously, this is not actually the case, as can be seen from the modified test illustrating such a scenario. If an underlying source can enqueue multiple chunks synchronously, then it should just do so! It shouldn't wait to be pull()ed, as the previous test was doing.
domenic added a commit that referenced this issue Feb 5, 2015
See discussion in #185 for background.

This allows more convenient integration with promise-returning underlying source functions, removing the need for `.catch(error)` as seen in the modified example.

It also rationalizes the slightly-strange way in which pull() is mutexed based on whether enqueue() has been called: instead, it is now mutexed so as to not be called again until the previous iteration's promise has fulfilled. This gives more direct control to the underlying source, and allows them to deal more easily with cases where there are just no chunks to enqueue.

Although superficially it seems this may reduce performance for cases where underlying sources can enqueue many chunks synchronously, this is not actually the case, as can be seen from the modified test illustrating such a scenario. If an underlying source can enqueue multiple chunks synchronously, then it should just do so! It shouldn't wait to be pull()ed, as the previous test was doing.
isonmad pushed a commit to isonmad/streams that referenced this issue Sep 18, 2016
Align with the WritableStream API.

This resolves issues whatwg#518, whatwg#498, whatwg#331, and whatwg#185.
isonmad pushed a commit to isonmad/streams that referenced this issue Sep 19, 2016
Align with the WritableStream API.

This resolves issues whatwg#518, whatwg#498, whatwg#331, and whatwg#185.
isonmad pushed a commit to isonmad/streams that referenced this issue Sep 19, 2016
Align with the WritableStream API.

This resolves issues whatwg#518, whatwg#498, whatwg#331, and whatwg#185.
tyoshino pushed a commit that referenced this issue Oct 12, 2016
Move TransformStream to promises based api (#519)

Fix a bunch of bugs along the way and add tests for them.

Closes #518, #498, #331, and #185.
@domenic domenic closed this as completed Oct 12, 2016
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Development

No branches or pull requests

3 participants