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

Pause readable stream reader #1651

Closed
osintalex opened this issue May 5, 2023 · 8 comments
Closed

Pause readable stream reader #1651

osintalex opened this issue May 5, 2023 · 8 comments

Comments

@osintalex
Copy link

osintalex commented May 5, 2023

As far as I know, once you've started reading a stream from response.body you can't pause and resume it like you can with node.

I think this would be very useful. I've tested this with the below React code sending back a stream of JSON from an app I'm running locally. I thought that if I used sleep in a blocking way it would stop the reader from reading but I noticed that it keeps reading in the background.

Perhaps I've made a mistake here and there is a way to achieve this in pure JS, I'd be really grateful for an example of that if so!

import logo from "./logo.svg";
import "./App.css";

function App() {
  let pause = false;
  let cancel = false;
  function changePause() {
    pause = !pause;
  }
  function cancelStream() {
    cancel = true;
  }
  function sleep(ms) {
    return new Promise((resolve) => setTimeout(resolve, ms));
  }

  async function* iterateOverStream(stream) {
    const reader = stream.getReader();
    const decoder = new TextDecoder();

    while (true) {

      if (pause) {
        // Slow down the infinite loop repeating until pause is pressed again
        // I thought that this would pause the reader from reading but it doesn't
        // it just pauses displaying the data, the reader keeps reading in the background
        await sleep( 3 * 1000);
        continue;
      } else {
        const { done, value } = await reader.read();
        if (done) break;
        if (cancel) {
          reader.cancel();
          break;
        }
        yield decoder.decode(value, { stream: !done });
      }
    }
    reader.releaseLock();
  }

  async function readData() {
    cancel = false;
    const response = await fetch("http://localhost:5000");
    for await (const chunk of iterateOverStream(response.body)) {
      console.log(`json chunk is ${chunk}`);
    }
  }

  return (
    <div className="App">
      <header className="App-header">
        <img src={logo} className="App-logo" alt="logo" />
        <p onClick={readData}>
          Click me and check out dev tools to test stream
        </p>
        <p onClick={changePause}>Click to pause/unpause</p>
        <p onClick={cancelStream}>Click to cancel stream</p>
      </header>
    </div>
  );
}

export default App;
@annevk
Copy link
Member

annevk commented May 6, 2023

cc @saschanaz @MattiasBuelens

@saschanaz
Copy link
Member

Do you have the full code including the server part? Looks like it should stop reading at some point, could be a browser bug.

@osintalex
Copy link
Author

osintalex commented May 8, 2023

Hey! Thanks for the reply - sever code is below. It's a very simple Flask app in Python that gives results like this:

{
	"items": [
		{
			"hello": "world"
		},
		...
		{
			"hello": "world"
		}
	]
}

To be clear, it does stop reading when it's reached the 2000th item. But I'm looking for a way to pause and resume it any arbitrary point in the stream.

import json
from flask import Flask, Response
from flask_cors import CORS


app = Flask(__name__)
CORS(app)


@app.route('/')
def home():

    def my_generator():
        yield '{"items": ['
        for count in range(1, 2001):
            my_json_object = json.dumps({"hello": "world"})
            if count == 2000:
                yield my_json_object
            else:
                yield my_json_object + ", "
        yield ']}'
    return Response(my_generator(), status=200, content_type='application/json')


if __name__ == '__main__':
    app.run(debug=True)

@saschanaz
Copy link
Member

saschanaz commented May 8, 2023

Looks like all browser engines immediately fetch every byte from fetch("http://localhost:5000") even without accessing .body at all. And I'm not familiar with how the fetch suspension works exactly. @jesup, can you help?

(I tweaked the number to 200001 and 200000 and browsers still fetched all 40 MB data)

@ricea
Copy link
Collaborator

ricea commented May 17, 2023

I believe the reason for continuing to read from the network even when JavaScript has stopped reading is to ensure that the HTTP cache is populated.

Try adding a Cache-Control: no-store header to your response. This avoids writing to the HTTP cache, and so should enable reading the response to be paused.

@saschanaz

This comment was marked as off-topic.

@saschanaz
Copy link
Member

Forget my previous comment, it's no-store and this works:

r = Response(my_generator(), status=200, content_type='application/json')
r.cache_control.no_store = True
return r

Hope this helps!

@osintalex
Copy link
Author

Thanks so much everyone! Really helpful responses, I would never have figured this out on my own.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Development

No branches or pull requests

4 participants