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
Error: You cannot pipe after data has been emitted from the response. #887
Comments
Also seeing this under similar circumstances. |
I don't know what other implications it might have, but I am able to solve this issue by altering Add: var superOn = Request.prototype.on;
Request.prototype.on = function (eventName) {
if (eventName === "data") {
this.resume()
}
superOn.apply(this, arguments)
} And change the dataStream.on("data", function (chunk) {
var emitted = self.emit("data", chunk)
if (emitted) {
self._destdata = true
} else {
// pause URL stream until we pipe it
dataStream.pause()
dataStream.unshift(chunk)
}
}) The basic issue (I think) is that Would be good if someone else could test this solution and verify that it works. |
Nah, doesn't work for me. Due to the lack of activity on this issue, i'm just going to stop using |
I should mention that my fix is for request 2.37.0. It would not work with an older release. |
@aldeed I have forked the repository and applied your fix: https://github.com/joepie91/request. Depending on the degree to which this (original) repository is maintained in the future, I might merge more fixes into there. I'm not sure yet. I'd been debugging this issue for a few hours now, but was unable to find a solution until I read this thread. Some more technical background information on why this problem is occurring follows. It's a bit simplified, to make it easier to follow along.
There's only one problem: as soon as you attach to the Now this is normally not an issue; Node.js works with 'ticks' - consider them 'cycles' in the interpreter. The simplified explanation is that one block of code will consume a 'tick', and no events will be checked or fired until the next tick. This means that the following hypothetical code will work fine: stream = getSomeRequestStream();
stream.pipe(target); Both lines are executed in the same tick, and the first Now, what @joe-spanning attempted to do was use an asynchronous construct, a timeout in this particular example. The stream initialization and Asynchronous constructs follow the same rule as events - they can't possibly execute until the next tick. However, by the time your asynchronous construct executes, the event cycle has just finished and the first This same problem occurs for anything that happens at least one tick later than the stream initialization. The solution by @aldeed fixes this by checking if there are any listeners attached to the request object, and if not, putting the chunk of data back in the original stream and pausing that original stream - thus stopping it from flowing. Once a listener is attached, it then resumes the original stream, and this time there will be a The way the One last note; if you define a callback in your request initialization, this fix will not work. Internally, if a callback is supplied, @aldeed I've been trying to fix this for hours, with all my attempts to actually fix it failing, and I can now finally continue working on my thing. I owe you a beer :) |
A quick note about this issue as it relates to new-style stream and the 3.0 branch. With new-style readable streams they are opened in what could be considered a "paused & buffering" state, they won't let out data until they are This means we'll have to ditch most of the nextTick() hacks. I've dealt with this a bit in other libraries and what we'll have to do is wait for the first read() and then trigger all the "lazy" code. One obvious consequence is that in 3.0 if you pass a callback it will buffer all the content. Currently, if you access the streaming API it disables buffering but without the nextTick hack we can't infer that. |
@Mikael Would you accept this fix as a pull request for the current master branch? |
I'd need to see it as a PR in order to evaluate it. |
Alright. It's probably best to leave the actual pull request creation up to @aldeed, given that he contributed the original fix - having it merged into the main repository should probably be his choice, licensing-wise and all that. |
Thanks, @joepie91. @mikeal, as I recall, the way I read the code in 2.37.0, it's already using new-style streams so they are "paused and buffered", as you say, initially. The problem comes when you attach your own "data" listener and call resume, which you do in different places, because these tell the readstream to unpause itself. So as @joepie91 says, a true solution would require re-engineering the internals such that you don't attach a "data" listener. From the docs:
|
2.37 is definitely not using new-style streams :) You must pipe the request object to a destination before it starts emitting data events. Similarly, you must pipe to a request instance in the same tick that you create it or else similar tricks will fail (although we have some code to try and mitigate this in some cases, but not all). |
OK, I probably misread or I'm misremembering or I looked at 3.0 code, too, and got confused. :) |
The quickest fix might be to pipe immediately to a |
@ZJONSSON I did try that at some point, but it didn't work either. As far as I understand, it's prone to the same limitation (no re-emission of data emitted prior to a later stream attachment) as any other kind of stream, so you'd still be stuck if the original attachment by |
@joepie91 Taking the original example at the top and piping immediately to a PassThrough runs without problems. Maybe I'm misunderstanding the issue? var fs = require('fs'),
request = require('request');
var readStream = request({
url: 'https://gist.githubusercontent.com/joe-spanning/6902070/raw/8967b351aec744a2bb51c16cb847c44636cf53d9/pipepromise.js'
})
.pipe(require('stream').PassThrough()); // this line is the only modification
// wait for 5 seconds, then pipe
setTimeout(function() {
var writeStream = readStream.pipe(fs.createWriteStream('test.js'));
writeStream.on('finish', function() {
console.log('all done!');
});
writeStream.on('error', function(err) {
console.error(err);
});
}, 5000); |
I honestly have no idea. If I recall correctly, that is exactly what I tried, and it didn't help. Has the underlying code in I should add that the example you modified is actually that of @joe-spanning. I'm not sure if there may be a difference between that and my code that makes it only work in his case. |
Submitted PR #1098 |
I just got bitten by |
I have the same problem except, I get this error when I try to req = request(options)
req.on 'response', (response) ->
# get info from headers here
stream = createWriteStream file
req.pipe stream |
Update from my side, not sure why I didn't post this before. Due to this issue not being resolved timely and running across a number of other frustrating bugs in |
Is there an update on this? |
I know this issue is old, but in my case (similar to @dogancelik, piping a response conditionally based on the response headers), I was able to do the following: const req = request.get(URL);
req.on('response', (response) => {
response.pause();
// Do stuff with response.statusCode etc.
setTimeout(() => {
response
.pipe(process.stdout)
;
}, 2000);
}); i.e., pausing the response stream ( I'd welcome feedback from users more knowledgeable than me about this. |
Just come across this too... I solved it from my end as it was a logical error in my case. I have a global array where I store each request via a unique id. I'm running multiple requests simultaneously. I do have an abort option for the requests. This may give you more info;
|
@julien-c Good call, this indeed seems to be because binding a Not sure if this is intended or not (my understanding was that this isn't how it was supposed to work). For now, I'll plan on using the workaround of pausing the stream (I'll try & remember to post back here if I find out anything new along the way) |
I'm randomly experiencing the same like dogancelik in the comment above: The error occurs when piping inside the response handler. But how can this be possible? If you look at request.js, the "response" event is emitted BEFORE request's own "data" handler (which causes the trouble) is attached at all:
As the event handlers are called synchronously I expect the pipe to be attached before data is consumed. |
I get the same err with following code:
And I work arount id by using
|
The similar bug exists for request object also. If you did I also switched to core http/https modules for streams. It is easy and reliable. So |
Don't pipe tel response happened downloadFile(file_url , targetPath){
/////////////// BE INSIDE req.on('response', function ( data )///////
}` |
Request is now in maintenance mode and wont be merging any new features. Please see #3142 for more information. |
Can confirm that the solution from julien-c works for We had a similar setup with a Promise instead of setTimeout, where the function getReadStream() {
return new Promise((resolve, reject) => {
const req = request.get('...');
req.on('response', (response) => {
response.pause(); // -> this solves the issue
if (response.statusCode === 200) {
resolve(response);
} else {
reject(new Error('Error ' + response.statusCode));
}
});
req.on('error', reject);
});
}
// somewhere else
const readStream = await getReadStream();
readStream.pipe(writeStream);
}
|
I am encountering an issue where I will get the "You cannot pipe after data has been emitted from the response." error when trying to pipe to a WriteStream. This only occurs when there has been a delay between when the
request()
call was made, and when thereadStream.pipe(writeStream)
call is made.Node version: v0.10.24
Request version: 2.34.0
I created a github repo with the source that reproduces the problem:
https://github.com/joe-spanning/request-error
Code is also below for convenience:
You might need to play with the timeout value to reproduce the error. Longer timeouts increase the probability that the error will occur.
The text was updated successfully, but these errors were encountered: