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

[WIP] Response now accepts ReadableStream as body #404

Closed
wants to merge 22 commits into from

Conversation

jimmywarting
Copy link

@jimmywarting jimmywarting commented Oct 5, 2016

This is a part necessary to be able to start supporting streaming response (#198)

here is just an example of how to use it

var rs = new ReadableStream({
  start(controller) {
    var chunk = new Uint8Array(3).fill(97)
    controller.enqueue(chunk)
    controller.close()
  }
})

var res = new Response(rs)

res.text().then(console.log) // aaa
res.arrayBuffer().then(console.log) // ArrayBuffer [97, 97, 97]
res.blob().then(console.log) // Blob {size: 3}
var reader = res.body.getReader()

This PR don't make use of ReadableStream it just allows end user to be able to construct a Response with a ReadableStream as body as you should be able to do

Copy link
Contributor

@mislav mislav left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thanks, this looks like a good start! Will users of streaming need any polyfills to be able to make use of this? Can you show off some example usage scenario in code?

package.json Outdated
@@ -6,7 +6,7 @@
"repository": "github/fetch",
"license": "MIT",
"devDependencies": {
"bower": "1.3.8",
"bower": "^1.7.9",
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Is this change necessary?

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Maybe not. But I got a warning saying one of Bowers depends was deprecated

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Deprecation warnings are fine. They don't affect us at all.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Please undo the commit that made the change to bower version. It doesn't belong in this PR and was never necessary. As I said, deprecation warnings are acceptable.

fetch.js Outdated
return reader.read().then(function(result){
if(!result.done){
chunks.push(result.value)
return pump()
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Is it possible to create a while loop rather than recursion? I guess not, because of async.

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Don't think so

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This is just for turning a stream into a blob/text/arraybuffer

We don't know in advance if they want a stream/blob/text or a arraybuffer. So I thought the best default responseType should be ms-stream or moz-chunked-arraybuffer instead of blob so we build all our Response method based on streams first

fetch.js Outdated
var pump = function(){
return reader.read().then(function(result){
if(!result.done){
chunks.push(result.value)
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Doesn't it defeat the point of streaming if we've just read the entire response into memory at once?

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Mm, no? You call blob, text or arraybuffer so you need to acembly all the chunks into one result.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Fair point!

@jimmywarting
Copy link
Author

jimmywarting commented Oct 6, 2016

Thanks, this looks like a good start! Will users of streaming need any polyfills to be able to make use of this?

They need web-stream-polyfill if they want to use streams. ATM it's like an optional dependency.

Can you show off some example usage scenario in code?

Can show some example tomorrow. Just realized ’_bodyStream’ should just be named ’body’ to follow the spec

@@ -177,6 +177,8 @@
this._bodyText = body.toString()
} else if (!body) {
this._bodyText = ''
} else if (body.getReader) {
this.body = body
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Are we now allowing people to interface with response.body directly? Is this in the spec?

Copy link
Author

@jimmywarting jimmywarting Oct 6, 2016

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yep, body is suppose to be a ReadableStream. You can test this directly in chrome's console log

res = new Response(new ReadableStream({}))
reader = res.body.getReader()
console.log(reader)

// even this works:
res = new Response(new Blob([]))
reader = res.body.getReader()
console.log(reader)

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It's also suppose to solve abortable request by calling res.body.cancel() So literally every thing you pass into new Response should be transformed to ReadableStream IMO.

But I don't want to rush into things and change the hole polyfill

fetch.js Outdated
var pump = function(){
return reader.read().then(function(result){
if(!result.done){
chunks.push(result.value)
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Fair point!

package.json Outdated
@@ -6,7 +6,7 @@
"repository": "github/fetch",
"license": "MIT",
"devDependencies": {
"bower": "1.3.8",
"bower": "^1.7.9",
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Deprecation warnings are fine. They don't affect us at all.

Copy link
Contributor

@mislav mislav left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thanks, this looks pretty good! One final thing we would need is tests for this. It's fine if the test suite imports a polyfill for streams. Ideally these tests would work in PhantomJS as well as a real browser.

fetch.js Outdated
@@ -204,6 +206,21 @@

if (this._bodyBlob) {
return Promise.resolve(this._bodyBlob)
} else if (this.body) {
var reader = this.body.getReader();
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Minor: we don't use semicolons

fetch.js Outdated
} else if (this.body) {
var reader = this.body.getReader();
var chunks = [];
var pump = function(){
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Minor: we usually put a space in between ) and { (same applies to next line)

fetch.js Outdated
var chunks = [];
var pump = function(){
return reader.read().then(function(result){
if(!result.done){
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Minor: we usually put a space after if

fetch.js Outdated
@@ -216,6 +233,10 @@
}

this.text = function() {
if (this.body) {
return this.blob().then(readBlobAsText)
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We have some checks in place (see consumed() below) to prevent reading the body data multiple times. Should the same restrictions also apply to readable streams?

@jimmywarting
Copy link
Author

Will try to add some test and fix the minor issues.
The stream api already has some sort of consumed restriction in a way that you can not call body.getReader() twice...

@jimmywarting
Copy link
Author

Just discovered this:

rs = new ReadableStream()
res = new Response(rs)
reader = res.body.getReader()
console.log(res.bodyUsed) // false
reader.read()
console.log(res.bodyUsed) // true

@jimmywarting
Copy link
Author

jimmywarting commented Oct 6, 2016

Think I'm going to need a little bit of help with the test. Got a error:
Uncaught Error: Out of skills            VM397:1

@mislav
Copy link
Contributor

mislav commented Oct 10, 2016

Not sure what that error is about, but PhantomJS is definitely going to need some ES shims before it can load the streams polyfill. PhantomJS has neither Symbol nor Object.assign support. Your test, however, runs but fails in Chrome with a different error. Perhaps you should work on making the test work in a real browser, and we can handle eventually getting it to run in PhantomJS.

@mislav
Copy link
Contributor

mislav commented Oct 10, 2016

@jimmywarting Here are my polyfills for PhantomJS. It still fails with a cryptic exception that's different than yours.

@jimmywarting
Copy link
Author

Works in the browser now...

@jimmywarting
Copy link
Author

jimmywarting commented Oct 10, 2016

Weird, running make test locally yields

  91 passing (196ms)
  8 pending
  0 failure 

even the browser succeeded

EDIT script don't exit properly
Can't figure it out...
If the only thing that is included in the test is new ReadableStream() then the test won't exit...
However running $ PhantomJS hello-world.js with the polyfills necessary and a simple new ReadableStream() do cuz the script to exit without errors
Could it be that there is something mocha inject into all code like code-coverage do to test if all code has been executed?

I know Node runs until all event queues are empty could something like this be the cause?

@jimmywarting
Copy link
Author

Do you have any idea how to tackle this? The test passes in browser but not in phantomjs

@mislav
Copy link
Contributor

mislav commented Oct 14, 2016

Not sure. We could just exclude this test from PhantomJS then. But that's a shame, since this test won't ever get exercised in CI, and we risk this functionality falling out of date during future changes.

@jimmywarting
Copy link
Author

This looks good to me now...

Copy link
Contributor

@mislav mislav left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

There are several reasons why I feel this PR is not ready for merge yet, in addition to my individual comments below:

  • It adds the response.body property conditionally, meaning it won't be present unless the response is streamable. I have to check the spec about this, but I kind of feel that this property should always be available.
  • The tests are currently not confirming that the behavior is indeed streaming, just that response.body was present. Can we add something that asserts that we can use the streaming functionality?

fetch.js Outdated
@@ -177,6 +194,14 @@
this._bodyText = body.toString()
} else if (!body) {
this._bodyText = ''
} else if (body.getReader) {
this.body = body
Object.defineProperty(this, 'bodyUsed', {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'm not a fan of the more complex bodyUsed implementation, but I guess if the value of this property needs to be dependent on the status of body, and body object can be accessed and manipulated directly, then we can't avoid having the dynamic accessor. 🤔

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I wasn't a big fan of this either, I even asked the community if it was okey to do this: whatwg/fetch#396

But it wasn't... We should maybe implement a new TypeError message that checks the locked property.
It's a bit more complicated to set the bodyUsed property since it needs set it upon the first getReader().read call and spying on this is not that easy...

var clone

if (this.bodyUsed) {
throw new TypeError('Failed to execute \'clone\' on \'Response\': Response body is already used')
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

If you used double quotes for the string, you wouldn't have to escape every single quote inside the string

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I used it first but then the linter complained about using 2 different quotes...

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Could as well fall thought and use tee()'s error message that already checks if stream is locked

@@ -8,6 +8,8 @@
<body>
<div id="mocha"></div>
<script src="/node_modules/url-search-params/build/url-search-params.js"></script>
<script src="/test/es6-shims.js"></script>
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

You can remove the commits that add es6-shims since that was intended for PhantomJS and we couldn't make it run in the end anyway

// Firefox & Edge partially support fetch (but don't have stream support)
// However the polyfilled Response do support it (With web-streams-polyfill)
// PhantomJS can use the polyfill but failes togheter with mocha
featureDependent(test, !isPhantomJS && support.stream, 'Response should accept ReadableStream', function() {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Instead of doing two separate checks !isPhantomJS && support.stream, support.stream should be false in the first place if the browser is PhantomJS. That simplifies this check.

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

done

package.json Outdated
@@ -6,7 +6,7 @@
"repository": "github/fetch",
"license": "MIT",
"devDependencies": {
"bower": "1.3.8",
"bower": "^1.7.9",
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Please undo the commit that made the change to bower version. It doesn't belong in this PR and was never necessary. As I said, deprecation warnings are acceptable.

@jimmywarting
Copy link
Author

jimmywarting commented Oct 16, 2016

It adds the response.body property conditionally, meaning it won't be present unless the response is streamable. I have to check the spec about this, but I kind of feel that this property should always be available.

I would like response.body to always be there, But that would mean that this fetch polyfill will need to be dependent on some ReadableStream polyfill - I would be fine with that cuz that is what I'm after and why I'm trying to help you implement this.
If it was included then we could get rid of _bodyText, _bodyBlob, _bodyFormData and everything else since the bodyInit would always have to be transformed into a ReadableStream

Maybe we could implement some super-duper simple ReadableStream class that adds the minimal requirements with enqueue, getReader, close, cancel, read leaving everything like back pressure, piping and queuing strategy out

The logic around res.arrayBuffer(), res.blob() & res.text() could be implemented way more simpler if everything where built on top of ReadableStream (that is what i like most about jonnyreeves version that he is starting of from ReadableStream)
jonnyreeves/fetch-readablestream#3

@jimmywarting jimmywarting changed the title Response now accepts ReadableStream as body [WIP] Response now accepts ReadableStream as body Oct 16, 2016
@eko24
Copy link

eko24 commented Dec 27, 2016

@mislav any chances to see this changes in master ?

@mislav
Copy link
Contributor

mislav commented Dec 27, 2016

@eko24 Not at the present moment. I'm mostly away from my computer around the New Year and I have to re-review this once I get back. We definitely want to support this, but I need to study the spec around this myself and see if we can minimize the diff further. The soonest I might be able to get back to this is 2 weeks from now.

@eko24
Copy link

eko24 commented Dec 27, 2016 via email

Update dependencies to enable Greenkeeper 🌴
@boczeratul
Copy link

hello!
any update on this one?

greenkeeper bot and others added 2 commits September 10, 2018 10:59
@mislav
Copy link
Contributor

mislav commented May 21, 2019

@jimmywarting Thank you for your contribution and sorry that we have never reached a resolution regarding this! I have felt uneasy about all the code added to support this feature, which might prove to be tricky to maintain in the future and also I don't really see it being widely used. I'm calling this polyfill basically feature-frozen now #198 (comment)

@mislav mislav closed this May 21, 2019
@jimmywarting
Copy link
Author

roger that

@github-actions github-actions bot locked as resolved and limited conversation to collaborators Oct 1, 2020
@ghost ghost deleted a comment Oct 21, 2022
@ghost ghost deleted a comment Oct 21, 2022
Sign up for free to subscribe to this conversation on GitHub. Already have an account? Sign in.
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

4 participants