xersiee
xersiee

Reputation: 4472

Wrapping node.js request into promise and piping

Here is my scenario: I want to get some external resource (binary file) using request library and pipe it to the client of my own application. If response code is != 200 or there are problems reaching remote server, I want to intercept and provide custom error message instead. Ideally, if response is fine, I want original headers to be preserved.

I was able to achieve that with the first piece of code I've pasted below. However, my whole application is based on Promise API so I wanted to make it consistent and wrap it in promise too. And when I do that, it no longer works. Firstly, I tried to achieve that with request-promise, without success. Then I tried to prepare very simple example on my own, still no luck.

Working example

var r = request.get('http://foo.bar');
r.on('response', result => {
  if (result.statusCode === 200) {
    r.pipe(res);
  } else {
    res.status(500).send('custom error message')
  }
});
r.on('error', error => res.status(500).send('custom error message');

Not working example

var r;
return new Promise((resolve, reject) => {
  r = request.get('http://foo.bar');
  r.on('response', result => {
    if (result.statusCode === 200) {
      resolve();
    } else {
      reject()
    }
  });
  r.on('error', reject);
}).then(() => {
  r.pipe(res);
}).catch(() => {
  res.status(500).json('custom error message');
});

By 'not working' I mean - no response is delivered, request is pending until timeout.

I've changed the code to call .pipe() on result passed to resolve instead of r. It responds to client, but response is empty then.

At the end, I've tried replacing request lib with simply http.get(). And with that, server returns file to the client, but headers (like Content-Type or Content-Length) are missing.

I've googled a lot, tried several request versions... and nothing is working.

Upvotes: 0

Views: 2885

Answers (1)

Jonas Wilms
Jonas Wilms

Reputation: 138457

The problem is that when "response" is triggered, you create a new promise that resolves immeadiately, but the then callback is always executed asynchronously, and when it gets called the file has arrived at the server, and there is no data flowing through the stream anymore. Instead you could just use the body parameter of the callback:

request.get('http://foo.bar', function(request, response, body) {
  if(response.statusCode === 200) {
    res.end(body);
  } else {
    res.status(500).end();
  }
});

For working with streams request seems a bit buggy, axios seems to do it better:

axios.get("http://foo.bar"', {
  validateStatus: status => status === 200,
  responseType: "stream"
}).then(({data: stream}) => {
  stream.pipe(res);
}).catch(error => {
   res.status(500).json(error);
});

Upvotes: 3

Related Questions