Repository: parse-community/Parse-SDK-JS
Pull request #1030 (2019-12-11 00:43:13)

Support query.cancel() on Node
Closes: https://github.com/parse-community/Parse-SDK-JS/issues/1021, https://github.com/parse-community/Parse-SDK-JS/pull/1027

Moves abort event to prevent xhr.open from calling it.
Ensure requestTask is set after the request sends.

* Improves browser support

codecov[bot]: # [Codecov](https://codecov.io/gh/parse-community/Parse-SDK-JS/pull/1030?src=pr&el=h1) Report
> Merging [#1030](https://codecov.io/gh/parse-community/Parse-SDK-JS/pull/1030?src=pr&el=desc) into [master](https://codecov.io/gh/parse-community/Parse-SDK-JS/commit/bdbd385a83d5a34c792aa7ccd86973d27ea660d6?src=pr&el=desc) will **decrease** coverage by `0.01%`.
> The diff coverage is `83.33%`.

[![Impacted file tree graph](https://codecov.io/gh/parse-community/Parse-SDK-JS/pull/1030/graphs/tree.svg?width=650&token=8nCENM0QHj&height=150&src=pr)](https://codecov.io/gh/parse-community/Parse-SDK-JS/pull/1030?src=pr&el=tree)

```diff
@@            Coverage Diff             @@
##           master    #1030      +/-   ##
==========================================
- Coverage   92.05%   92.03%   -0.02%     
==========================================
  Files          53       53              
  Lines        5112     5112              
  Branches     1142     1141       -1     
==========================================
- Hits         4706     4705       -1     
- Misses        406      407       +1
```


| [Impacted Files](https://codecov.io/gh/parse-community/Parse-SDK-JS/pull/1030?src=pr&el=tree) | Coverage Δ | |
|---|---|---|
| [src/ParseQuery.js](https://codecov.io/gh/parse-community/Parse-SDK-JS/pull/1030/diff?src=pr&el=tree#diff-c3JjL1BhcnNlUXVlcnkuanM=) | `96% <100%> (-0.15%)` | :arrow_down: |
| [src/RESTController.js](https://codecov.io/gh/parse-community/Parse-SDK-JS/pull/1030/diff?src=pr&el=tree#diff-c3JjL1JFU1RDb250cm9sbGVyLmpz) | `84.21% <71.42%> (-0.31%)` | :arrow_down: |

------

[Continue to review full report at Codecov](https://codecov.io/gh/parse-community/Parse-SDK-JS/pull/1030?src=pr&el=continue).
> **Legend** - [Click here to learn more](https://docs.codecov.io/docs/codecov-delta)
> `Δ = absolute <relative> (impact)`, `ø = not affected`, `? = missing data`
> Powered by [Codecov](https://codecov.io/gh/parse-community/Parse-SDK-JS/pull/1030?src=pr&el=footer). Last update [bdbd385...41cb82a](https://codecov.io/gh/parse-community/Parse-SDK-JS/pull/1030?src=pr&el=lastupdated). Read the [comment docs](https://docs.codecov.io/docs/pull-request-comments).

dplewis: @JeromeDeLeon This should fix your issue. Can you verify it?

Patch:
diff --git a/src/ParseQuery.js b/src/ParseQuery.js
index b84407d..b36414b 100644
--- a/src/ParseQuery.js
+++ b/src/ParseQuery.js
@@ -1837,10 +1837,13 @@ class ParseQuery {
    */
   cancel(): ParseQuery {
     if (this._xhrRequest.task && typeof this._xhrRequest.task.abort === 'function') {
+      this._xhrRequest.task._aborted = true;
       this._xhrRequest.task.abort();
+      this._xhrRequest.task = null;
+      this._xhrRequest.onchange = () => {};
+      return this;
     }
-    this._xhrRequest.task = null;
-    return this;
+    return this._xhrRequest.onchange = () => this.cancel();
   }
 
   _setRequestTask(options) {
diff --git a/src/RESTController.js b/src/RESTController.js
index 5e89bb7..385742c 100644
--- a/src/RESTController.js
+++ b/src/RESTController.js
@@ -51,9 +51,6 @@ if (typeof XDomainRequest !== 'undefined' &&
 function ajaxIE9(method: string, url: string, data: any, headers?: any, options?: FullOptions) {
   return new Promise((resolve, reject) => {
     const xdr = new XDomainRequest();
-    if (options && typeof options.requestTask === 'function') {
-      options.requestTask(xdr);
-    }
     xdr.onload = function() {
       let response;
       try {
@@ -82,6 +79,9 @@ function ajaxIE9(method: string, url: string, data: any, headers?: any, options?
     };
     xdr.open(method, url);
     xdr.send(data);
+    if (options && typeof options.requestTask === 'function') {
+      options.requestTask(xdr);
+    }
   });
 }
 
@@ -104,14 +104,10 @@ const RESTController = {
         );
       }
       let handled = false;
-      let aborted = false;
 
       const xhr = new XHR();
-      if (options && typeof options.requestTask === 'function') {
-        options.requestTask(xhr);
-      }
       xhr.onreadystatechange = function() {
-        if (xhr.readyState !== 4 || handled) {
+        if (xhr.readyState !== 4 || handled || xhr._aborted) {
           return;
         }
         handled = true;
@@ -132,8 +128,6 @@ const RESTController = {
           if (response) {
             promise.resolve({ response, status: xhr.status, xhr });
           }
-        } else if (aborted && xhr.status === 0) {
-          promise.resolve({ response: {}, status: 0, xhr });
         } else if (xhr.status >= 500 || xhr.status === 0) { // retry on 5XX or node-xmlhttprequest error
           if (++attempts < CoreManager.get('REQUEST_ATTEMPT_LIMIT')) {
             // Exponentially-growing random delay
@@ -167,9 +161,6 @@ const RESTController = {
       for (const key in customHeaders) {
         headers[key] = customHeaders[key];
       }
-      xhr.onabort = () => {
-        aborted = true;
-      };
       xhr.onprogress = (event) => {
         if(options && typeof options.progress === 'function') {
           if (event.lengthComputable) {
@@ -184,7 +175,18 @@ const RESTController = {
       for (const h in headers) {
         xhr.setRequestHeader(h, headers[h]);
       }
+      xhr.onabort = function () {
+        promise.resolve({
+          response: { results: [] },
+          status: 0,
+          xhr,
+        });
+      };
       xhr.send(data);
+
+      if (options && typeof options.requestTask === 'function') {
+        options.requestTask(xhr);
+      }
     }
     dispatch();
 


Test patch:
diff --git a/integration/test/ParseQueryTest.js b/integration/test/ParseQueryTest.js
index a65ee34..7a5be84 100644
--- a/integration/test/ParseQueryTest.js
+++ b/integration/test/ParseQueryTest.js
@@ -1998,4 +1998,17 @@ describe('Parse Query', () => {
     }, 0);
     assert.equal(result, 6);
   });
+
+  it('can cancel query', async () => {
+    const obj1 = new TestObject({ number: 1 });
+    const obj2 = new TestObject({ number: 2 });
+    const obj3 = new TestObject({ number: 3 });
+    await Parse.Object.saveAll([obj1, obj2, obj3]);
+
+    const query = new Parse.Query(TestObject);
+    query.find().then((results) => {
+      assert.equal(results.length, 0);
+    });
+    query.cancel();
+  });
 });
diff --git a/src/__tests__/ParseQuery-test.js b/src/__tests__/ParseQuery-test.js
index 4452ec9..0e7807a 100644
--- a/src/__tests__/ParseQuery-test.js
+++ b/src/__tests__/ParseQuery-test.js
@@ -2124,7 +2124,7 @@ describe('ParseQuery', () => {
 
     await query.find();
 
-    expect(query._xhrRequest.task).toEqual(mockRequestTask);
+    expect(query._xhrRequest.task).toEqual(null);
     query.cancel();
     expect(mockRequestTask.abort).toHaveBeenCalledTimes(1);
   });