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

Add progress events #322

Merged
merged 14 commits into from
Jul 23, 2017
Merged

Conversation

vadimdemedes
Copy link

@vadimdemedes vadimdemedes commented Jun 1, 2017

This PR adds upload and download progress events.

got.post('https://website.com', {body: fs.createReadStream('file')})
  .on('uploadProgress', progress => {
    // `progress` looks like:
    // {
    //   percent: 0.24,
    //   transferred: 24518,
    //   total: 50000
    // }
    }
  })
  .on('downloadProgress', progress => {
    // `progress` has the same shape as in `uploadProgress` event
  });

Initial upload/download progress event with percent equal to 0 is always emitted. If it's not possible to retrieve the total upload/download body size, percent and size.total are equal to null.

Hack for receiving upload progress events when uploading buffers or strings

When sending Buffer as body, there are only 2 uploadProgress events, because the whole buffer gets written to the connection, so req.connection.bytesWritten becomes equal to buffer length. To work around this limitation and receive meaningful progress events, intoStream module is used to convert buffer into stream.

One more quirk. Since after conversion, there's no more access to buffer's size, I'm making it accessible by assigning it to _buffer on the stream here https://github.com/sindresorhus/got/pull/322/files#diff-168726dbe96b3ce427e7fedce31bb0bcR525 and getting it later again here https://github.com/sindresorhus/got/pull/322/files#diff-168726dbe96b3ce427e7fedce31bb0bcR76.

TODO

  • Write a description for this PR
  • Explain the hack behind wrapping body buffer into a stream when uploading
  • Update readme

@vadimdemedes vadimdemedes force-pushed the progress-events branch 2 times, most recently from 63d6189 to 7af37a0 Compare June 1, 2017 18:42
@vadimdemedes vadimdemedes changed the title [WIP] Add progress events Add progress events Jun 1, 2017
index.js Outdated
transferred: uploaded,
total: uploadBodySize
});
}, 150);
Copy link
Owner

Choose a reason for hiding this comment

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

Why did we go with 150? Can you move 150 into a variable with a descriptive name of why it's 150.

Copy link
Author

Choose a reason for hiding this comment

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

We just picked a random number for frequency of upload progress events.

Copy link
Owner

Choose a reason for hiding this comment

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

Should definitely be clearer in the code then. 150 is very specific, wonder why we didn't just go with 100.

Copy link
Author

Choose a reason for hiding this comment

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

Np ;)

index.js Outdated
}

if (isFormData(body)) {
body.getLength((err, size) => {
Copy link
Owner

Choose a reason for hiding this comment

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

Would be nicer to use pify here and on line 66, so we don't have to wrap the whole thing in the Promise constructor.

index.js Outdated
getBodySize(opts)
.then(size => {
uploadBodySize = size;

Copy link
Owner

Choose a reason for hiding this comment

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

Remove the empty line here.

index.js Outdated

promise.on = (name, fn) => {
proxy.on(name, fn);

Copy link
Owner

Choose a reason for hiding this comment

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

Remove the empty line here.

index.js Outdated
@@ -388,7 +568,7 @@ function got(url, opts) {
}
}

got.stream = (url, opts) => asStream(normalizeArguments(url, opts));
got.stream = (url, opts) => asStream(Object.assign({}, normalizeArguments(url, opts), {stream: true}));
Copy link
Owner

Choose a reason for hiding this comment

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

Would be better to do the Object.assign thing inside the asStream function.

test/progress.js Outdated
t.is(lastEvent.percent, 0);
}

events.forEach((event, index) => {
Copy link
Owner

Choose a reason for hiding this comment

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

for (const [index, event] of events.entries()) {

test/progress.js Outdated
.on('end', () => {
checkEvents(t, events);
t.end();
});
Copy link
Owner

Choose a reason for hiding this comment

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

You could use https://github.com/sindresorhus/get-stream here to be able to use async/await instead of test.cb.

body.append('key', 'value');
body.append('file', file);

const size = await pify(body.getLength.bind(body))();
Copy link
Owner

Choose a reason for hiding this comment

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

pify binds to the original object by default, so the bind here is moot I think.

Copy link
Author

Choose a reason for hiding this comment

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

Nope, it fails without bind.

@vadimdemedes
Copy link
Author

vadimdemedes commented Jul 2, 2017

Thanks @sindresorhus, feedback addressed. Had to rebase and force push, because of a conflict.

@sindresorhus
Copy link
Owner

sindresorhus commented Jul 2, 2017

@vadimdemedes Thanks for the quick turn-around.

@sindresorhus
Copy link
Owner

Can you apply #322 (comment) to the other test.cb tests too?

@vadimdemedes
Copy link
Author

@sindresorhus Oh yeah, missed the other ones.

@@ -202,6 +203,21 @@ got.stream('github.com')

`redirect` event to get the response object of a redirect. The second argument is options for the next request to the redirect location.

##### .on('uploadProgress', progress)
##### .on('downloadProgress', progress)
Copy link
Contributor

Choose a reason for hiding this comment

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

Should probably clarify that these can be used with the Promise interface too. It's a little ambiguous now since they're documented under got.stream.

@sindresorhus
Copy link
Owner

@alextes @floatdrop I'll merge this on monday unless it gets any objections by then ;)

const body = opts.body;

if (opts.headers['content-length']) {
return Number(opts.headers['content-length']);
Copy link
Contributor

Choose a reason for hiding this comment

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

Just generally interested what the benefit is of wrapping this in a Number object?

Copy link
Owner

Choose a reason for hiding this comment

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

It's not wrapping it in a Number object, it's coercing it to a number.

Copy link
Contributor

Choose a reason for hiding this comment

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

Any benefit over parseInt? I never used Number directly so that's why I'm asking :).

Copy link
Owner

Choose a reason for hiding this comment

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

Mostly just consistency. I also use Boolean() to coerce something to a Boolean. Same with String().

clearInterval(progressInterval);

ee.emit('uploadProgress', {
percent: 1,
Copy link
Contributor

Choose a reason for hiding this comment

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

Why is percent set to 1? Probably missing something here :).

I'm also wondering why it is emitted? If some wants to calculate the percentage, he can easily do transferred / total. I would rather not have it emitted, but that might just be me :).

Copy link
Contributor

Choose a reason for hiding this comment

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

Is percent emitted because total can be null when streaming?

Copy link
Author

Choose a reason for hiding this comment

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

Why is percent set to 1? Probably missing something here :).

It's calculating percent to be from 0 to 1. So 1 means it's the last upload progress event and it's done.

I'm also wondering why it is emitted?

Initially it was only for convenience. But there are also cases where total can be unavailable (like you said, when streaming), so got makes sure that percent is always available.

readme.md Outdated

```js
{
percent: 0.24,
Copy link
Contributor

Choose a reason for hiding this comment

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

1024 bytes send against 10240 in total means 10% and thus percent should be 0.1.

Vadim Demedes and others added 2 commits July 22, 2017 13:08
@sindresorhus sindresorhus merged commit be7b802 into sindresorhus:master Jul 23, 2017
@sindresorhus
Copy link
Owner

Yay! Vadim, your sambuca-powered work is finally merged. Thanks for working on it. Good times.

leo-excited

@sindresorhus
Copy link
Owner

@floatdrop @alextes Feel free to add your review still. I'm not gonna do a release yet.

@kevva
Copy link
Contributor

kevva commented Jul 23, 2017

const headersSize = Buffer.byteLength(req._header);
uploaded = req.connection.bytesWritten - headersSize;

// Prevent the known issue of `bytesWritten` being larger than body size
Copy link
Contributor

@alextes alextes Jul 23, 2017

Choose a reason for hiding this comment

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

@vadimdemedes if you have a link to an issue explaining what exactly is amiss and how simply ignoring any bytes uploaded beyond the body size is the right solution. Do share, I'd be happy to add it to the comment. If you don't let me know and I'll go looking myself.

Copy link
Author

Choose a reason for hiding this comment

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

There's no explanation, I just observed this bug randomly and couldn't find a source of the issue.

});

req.connection.on('connect', () => {
const uploadEventFrequency = 150;
Copy link
Contributor

Choose a reason for hiding this comment

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

Just curious, is there a reason behind this number? 6 2/3 times a second?

Copy link
Author

Choose a reason for hiding this comment

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

No reason. It was just a guess on how to report progress events not too frequently, but still enough.


progressInterval = setInterval(() => {
const lastUploaded = uploaded;
const headersSize = Buffer.byteLength(req._header);
Copy link
Contributor

Choose a reason for hiding this comment

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

Can header size change during upload? Otherwise, I don't see the point of putting this inside the interval or even the on 'connect'.

Copy link
Author

Choose a reason for hiding this comment

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

It can't change during upload, but there's no way to know when headers are sent, is there? So I placed it here to correctly calculate the body size.

@vadimdemedes vadimdemedes deleted the progress-events branch July 24, 2017 21:16
@vadimdemedes
Copy link
Author

@sindresorhus haha, best times!

I don't know why, but when I searched "sambucca" on Giphy, this is what it gave me, so now I'm posting this instead of my favorite drink to celebrate the merge:

@marianoguerra
Copy link

is there any plan to make a release with this change?

@sindresorhus
Copy link
Owner

Yes

jotto added a commit to jotto/got-lite that referenced this pull request Nov 30, 2017
brings npm pkg from 0.75MB to 0.45MB

after yarn autoclean --force, down to 0.25MB

from this PR: sindresorhus#284

next big savings is removing progress, from sindresorhus#322
jotto added a commit to jotto/got-lite that referenced this pull request Nov 30, 2017
brings npm pkg from 0.75MB to 0.45MB

after yarn autoclean --force, down to 0.25MB

from this PR: sindresorhus#284

next big savings is removing progress, from sindresorhus#322
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

6 participants