20

I need to implement a cancel-able client-side HTTP request in Node.js, without using external libraries. I'm giving a Promise object - cancellationPromise - which gets rejected when the cancellation is externally requested. This is how I know I may need to call request.abort().

The question is, should I be calling request.abort() only if https.request is still pending and response object is not yet available?

Or, should I be calling it even if I already got the response object and am processing the response data, like in the code below? In which case, will that stop any more response.on('data') events from coming?

  async simpleHttpRequest(url, oauthToken, cancellationPromise) {
    let cancelled = null;
    let oncancel = null;

    cancellationPromise.catch(error => { 
      cancelled = error; oncancel && oncancel(error) });

    try {
      const response = await new Promise((resolve, reject) => {
        const request = https.request(
          url.toString(), 
          { 
            method: 'GET', 
            headers: { 'Authorization': `Bearer ${oauthToken}` }        
          }, 
          resolve);

        oncancel = error => request.abort();
        request.on('error', reject);
        request.end();
      });

      if (cancelled) throw cancelled;

      // do I need "oncancel = null" here?
      // or should I still allow to call request.abort() while fetching the response's data?

      // oncancel = null;

      try {
        // read the response 
        const chunks = await new Promise((resolve, reject) => {
          response.on('error', reject);  
          const chunks = [];
          response.on('data', data => chunks.push(data));
          response.on('end', () => resolve(chunks));
        });

        if (cancelled) throw cancelled;
        const data = JSON.parse(chunks.join(''));
        return data;
      }
      finally {
        response.resume();
      }
    }
    finally {
      oncancel = null;
    }
  }
avo
  • 10,101
  • 13
  • 53
  • 81
  • 1
    You should abort the request only before getting response. Once you get the response, you should decide according the response whether you want to abort the request or you want to successful response, not abort the request straight away. – Akansh Jun 17 '19 at 10:13
  • @AkanshGulati, what if `cancellationPromise` is rejected after I got the response object but while I'm still receiving its data chunks? – avo Jun 17 '19 at 20:02

1 Answers1

20

It depends what you want to achieve by aborting a request.

Just a bit of background. HTTP 1 is not able to "cancel" a request it sends it and then waits for the response. You cannot "roll back" the request you did. You need a distributed transaction to do so. (Further reading.) As the MDN developer document states:

The XMLHttpRequest.abort() method aborts the request if it has already been sent. When a request is aborted, its readyState is changed to XMLHttpRequest.UNSENT (0) and the request's status code is set to 0.

Basically you stop the response from being processed by your application. The other application will probably (if you called abort() after it was sent to it) finish its processing anyways.

From the perspective of the question:

The question is, should I be calling request.abort() only if https.request is still pending and response object is not yet available?

TL.DR.: It only matters from the point of view of your application. As I glance at your code, I think it will work fine.

Hash
  • 4,647
  • 5
  • 21
  • 39
  • Thanks! Still curious though, in case I already got the `response` object and still call `request.abort`, do you know if that would stop upcoming `response.on('data')` events for the pending response data? I assume so because the [docs say](https://nodejs.org/api/http.html#http_request_abort) *"Marks the request as aborting. Calling this will cause remaining data in the response to be dropped and the socket to be destroyed."* I haven't verified that myself yet. Also, in this case, do I still need to call `response.resume()`? – avo Jun 19 '19 at 10:44
  • 1
    As far as I know it is not strictly specified, but in the past projects of mine the assumption you made was correct. Aka it stopped the `response.on(data)`. But we removed these code parts as it had no benefit for us. – Hash Jun 19 '19 at 14:02
  • 4
    It's OK to abort an HTTP GET or HEAD, but not POST or PUT. Once the caller did decide to abort, they're likely not expecting any of their hooks to be called and promises to get rejected, so I'd say always abort. – root Jun 23 '19 at 04:17
  • 1
    I agree with you to a certain degree. Mostly `HEAD` and `GET` do not alter data. The sad part is that I have seen systems in which they violate this... – Hash Jun 23 '19 at 11:14