Skip to content

Parse.Query.subscribe() and Parse.Query.first() don't work in Parse v3.5.0 #1596

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

Closed
4 tasks done
aarondill opened this issue Nov 4, 2022 · 16 comments · Fixed by #1600
Closed
4 tasks done

Parse.Query.subscribe() and Parse.Query.first() don't work in Parse v3.5.0 #1596

aarondill opened this issue Nov 4, 2022 · 16 comments · Fixed by #1600
Labels
state:released Released as stable version state:released-alpha Released as alpha version state:released-beta Released as beta version type:bug Impaired feature or lacking behavior that is likely assumed

Comments

@aarondill
Copy link

aarondill commented Nov 4, 2022

New Issue Checklist

Issue Description

Parse.Query.subscribe() and Parse.Query.first() throw errors upon calling
Query.first() is also recorded here

Steps to reproduce

Here is a Github Repository that includes files for use in testing this bug.
Create a Parse.Query and call subscribe() on it
alternatively create a LiveQueryClient and call subscribe() on it with a Parse.Query

Actual Outcome

throws error "Uncaught (in promise) TypeError: Cannot read properties of undefined (reading 'socket')" upon calling.

Expected Outcome

Expected a subscription object

Environment

Client

@parse-github-assistant
Copy link

parse-github-assistant bot commented Nov 4, 2022

Thanks for opening this issue!

  • 🚀 You can help us to fix this issue faster by opening a pull request with a failing test. See our Contribution Guide for how to make a pull request, or read our New Contributor's Guide if this is your first time contributing.

@mtrezza mtrezza added the type:bug Impaired feature or lacking behavior that is likely assumed label Nov 4, 2022
@mtrezza
Copy link
Member

mtrezza commented Nov 4, 2022

What version of Parse Server are you using?

@mtrezza
Copy link
Member

mtrezza commented Nov 4, 2022

See #1593 (comment)

@aarondill
Copy link
Author

aarondill commented Nov 5, 2022

What version of Parse Server are you using?

I am using back4app. I'm not quite sure how to check the current server version but based on the other relevant reports, I would assume it's 4.5.0

@nekonenene
Copy link

nekonenene commented Nov 6, 2022

I encountered the same problem.
https://github.com/nekonenene/back4app-test

const query = new Parse.Query('Person').addAscending('createdAt');
const firstPerson = await query.first();

This code makes the following error.

image

image

I can avoid this error by writing:

const query = new Parse.Query('Person').addAscending('createdAt');
const [firstPerson] = await query.limit(1).find();

This problem occurs even after changing to v3.4.4 or v3.0.0, so I suspect that it is not a problem with Parse itself, may be caused by a dependent library or back4app etc.

@mtrezza
Copy link
Member

mtrezza commented Nov 6, 2022

This problem occurs even after changing to v3.4.4 or v3.0.0, so I suspect that it is not a problem with Parse itself, may be caused by a dependent library or back4app etc.

What is the last version that works for you, where the problem is not occurring?

@aarondill
Copy link
Author

aarondill commented Nov 7, 2022

This problem occurs even after changing to v3.4.4 or v3.0.0, so I suspect that it is not a problem with Parse itself, may be caused by a dependent library or back4app etc.

When I changed to JS SDK v3.4.4, both first, and subscribe worked for me

@nekonenene
Copy link

nekonenene commented Nov 7, 2022

Oh, really?
Then it seems that 3.5.0 keeps running in my environment instead of 3.4.4. Thank you.

What is the last version that works for you, where the problem is not occurring?

I'm not sure because I haven't used Parse in the past. Sorry I can't help you.

@aarondill
Copy link
Author

aarondill commented Nov 7, 2022

Parse.Query.first() error reason and repair

Relevant code from each version for query.first:

Parse JS JDK v3.4.4:

{
    key: "first",
    value: function (options
    /*:: ?: FullOptions*/
    )
    /*: Promise<ParseObject | void>*/
    {
      var _this4 = this;

      options = options || {};
      var findOptions = {};

      if (options.hasOwnProperty('useMasterKey')) {
        findOptions.useMasterKey = options.useMasterKey;
      }

      if (options.hasOwnProperty('sessionToken')) {
        findOptions.sessionToken = options.sessionToken;
      }

      if (options.hasOwnProperty('context') && (0, _typeof2.default)(options.context) === 'object') {
        findOptions.context = options.context;
      }

      this._setRequestTask(findOptions);

      var controller = _CoreManager.default.getQueryController();

      var params = this.toJSON();
      params.limit = 1;
      var select = this._select;

      if (this._queriesLocalDatastore) {
        return this._handleOfflineQuery(params).then(function (objects) {
          if (!objects[0]) {
            return undefined;
          }

          return objects[0];
        });
      }

      return (0, _find.default)(controller).call(controller, this.className, params, findOptions).then(function (response) {
        var objects = response.results;

        if (!objects[0]) {
          return undefined;
        }

        if (!objects[0].className) {
          objects[0].className = _this4.className;
        } // Make sure the data object contains keys for all objects that
        // have been requested with a select, so that our cached state
        // updates correctly.


        if (select) {
          handleSelectResult(objects[0], select);
        }

        if (options.json) {
          return objects[0];
        } else {
          return _ParseObject.default.fromJSON(objects[0], !select);
        }
      });
    }
    /**
     *Comment describing eachBatch function here
     */
}

Parse JS JDK v3.5.0:

{
    key: "first",
    value: function (options
    /*:: ?: FullOptions*/
    )
    /*: Promise<ParseObject | void>*/
    {
      options = options || {};
      var findOptions = {};

      if (options.hasOwnProperty('useMasterKey')) {
        findOptions.useMasterKey = options.useMasterKey;
      }

      if (options.hasOwnProperty('sessionToken')) {
        findOptions.sessionToken = options.sessionToken;
      }

      if (options.hasOwnProperty('context') && (0, _typeof2.default)(options.context) === 'object') {
        findOptions.context = options.context;
      }

      this._setRequestTask(findOptions);

      var controller = _CoreManager.default.getQueryController();

      var params = this.toJSON();
      params.limit = 1;
      var select = this._select;

      if (this._queriesLocalDatastore) {
        return this._handleOfflineQuery(params).then(function (objects) {
          if (!objects[0]) {
            return undefined;
          }

          return objects[0];
        });
      }

      return (0, _find.default)(controller).call(controller, this.className, params, findOptions).then(function (response) {
        var objects = response.results;

        if (!objects[0]) {
          return undefined;
        }

        if (!objects[0].className) {
          objects[0].className = this.className;
        } // Make sure the data object contains keys for all objects that
        // have been requested with a select, so that our cached state
        // updates correctly.


        if (select) {
          handleSelectResult(objects[0], select);
        }

        if (options.json) {
          return objects[0];
        } else {
          return _ParseObject.default.fromJSON(objects[0], !select);
        }
      });
    }
    /**
     *Comment describing eachBatch function here
    */
}

Important changes

v3.4.4:

Start of query.first function:

var _this4 = this;

within return value.then():

if (!objects[0].className) {
	objects[0].className = _this4.className;
}

3.5.0

start of query.first function removes _this4 variable

within return value.then():

if (!objects[0].className) {
	objects[0].className = this.className;
}

Reason for error:

the return value of the first() function is:

return (0, _find.default)(controller).call(controller, this.className, params, findOptions).then(function (response) {...})

It is important to note that the function within the then call will be run with this set to undefined, due to strict mode; while _this4 in v3.4.4 is a Parse.Query object.

Due to this variation, while at first it seems to have the same result upon the removal of the _this4 variable in favor of directly using the this value, due to the use of the variable in a seperate scope, the this value resolves to undefined and this is trying to access undefined.className which (understandably) results in a typeError.

this change was likely made to save memory by the removal of a seemingly unnecessary variable, but due to the global scope of the then function, actually causes an error upon calling.

Solution:

The readdition of the _this4 variable to the top of the first function, and the modification of the assignment line to the code seen in v3.4.4 will resolve the error and return the correct ParseObject value.

the corrected code will be (identical to the v3.4.4 code):

{
    key: "first",
    value: function (options
    /*:: ?: FullOptions*/
    )
    /*: Promise<ParseObject | void>*/
    {
      var _this4 = this;

      options = options || {};
      var findOptions = {};

      if (options.hasOwnProperty('useMasterKey')) {
        findOptions.useMasterKey = options.useMasterKey;
      }

      if (options.hasOwnProperty('sessionToken')) {
        findOptions.sessionToken = options.sessionToken;
      }

      if (options.hasOwnProperty('context') && (0, _typeof2.default)(options.context) === 'object') {
        findOptions.context = options.context;
      }

      this._setRequestTask(findOptions);

      var controller = _CoreManager.default.getQueryController();

      var params = this.toJSON();
      params.limit = 1;
      var select = this._select;

      if (this._queriesLocalDatastore) {
        return this._handleOfflineQuery(params).then(function (objects) {
          if (!objects[0]) {
            return undefined;
          }

          return objects[0];
        });
      }

      return (0, _find.default)(controller).call(controller, this.className, params, findOptions).then(function (response) {
        var objects = response.results;

        if (!objects[0]) {
          return undefined;
        }

        if (!objects[0].className) {
          objects[0].className = _this4.className;
        } // Make sure the data object contains keys for all objects that
        // have been requested with a select, so that our cached state
        // updates correctly.


        if (select) {
          handleSelectResult(objects[0], select);
        }

        if (options.json) {
          return objects[0];
        } else {
          return _ParseObject.default.fromJSON(objects[0], !select);
        }
      });
    }
    /**
     *Comment describing eachBatch function here
     */
}

I have so far only looked at the code differences for Parse.Query.first(), but I will soon look at Parse.Query.subscribe() and see if I can find the issue(s) that result in that error. My suspicion is that it is a similar or the same issue.

@aarondill
Copy link
Author

aarondill commented Nov 7, 2022

Parse.Query.subscribe() Error Reason and Solution

Quick warning, this comment is nearly identical to the comment above. To save reading, the v3.5.0 update removed a _this2 variable that captures the this context at the beginning of the subscribe function, which is then referenced within a then statement, whose this value is equal to undefined due to running in strict mode.

Relevant code from each version for query.first:

Parse JS JDK v3.4.4:

{
    key: "subscribe",
    value: function (query
    /*: Object*/
    , sessionToken
    /*: ?string*/
    )
    /*: LiveQuerySubscription*/
    {
      var _this2 = this;

      if (!query) {
        return;
      }

      var className = query.className;
      var queryJSON = query.toJSON();
      var where = queryJSON.where;
      var fields = (0, _keys.default)(queryJSON) ? (0, _keys.default)(queryJSON).split(',') : undefined;
      var subscribeRequest = {
        op: OP_TYPES.SUBSCRIBE,
        requestId: this.requestId,
        query: {
          className: className,
          where: where,
          fields: fields
        }
      };

      if (sessionToken) {
        subscribeRequest.sessionToken = sessionToken;
      }

      var subscription = new _LiveQuerySubscription.default(this.requestId, query, sessionToken);
      this.subscriptions.set(this.requestId, subscription);
      this.requestId += 1;
      this.connectPromise.then(function () {
        _this2.socket.send((0, _stringify.default)(subscribeRequest));
      });
      return subscription;
    }
    /**
     * After calling unsubscribe you'll stop receiving events from the subscription object.
     *
     * @param {object} subscription - subscription you would like to unsubscribe from.
     */

  }

Parse JS JDK v3.5.0:

{
    key: "subscribe",
    value: function (query
    /*: Object*/
    , sessionToken
    /*: ?string*/
    )
    /*: LiveQuerySubscription*/
    {
      if (!query) {
        return;
      }

      var className = query.className;
      var queryJSON = query.toJSON();
      var where = queryJSON.where;
      var fields = (0, _keys.default)(queryJSON) ? (0, _keys.default)(queryJSON).split(',') : undefined;
      var subscribeRequest = {
        op: OP_TYPES.SUBSCRIBE,
        requestId: this.requestId,
        query: {
          className: className,
          where: where,
          fields: fields
        }
      };

      if (sessionToken) {
        subscribeRequest.sessionToken = sessionToken;
      }

      var subscription = new _LiveQuerySubscription.default(this.requestId, query, sessionToken);
      this.subscriptions.set(this.requestId, subscription);
      this.requestId += 1;
      this.connectPromise.then(function () {
        this.socket.send((0, _stringify.default)(subscribeRequest));
      });
      return subscription;
    }
    /**
     * After calling unsubscribe you'll stop receiving events from the subscription object.
     *
     * @param {object} subscription - subscription you would like to unsubscribe from.
     */

  },{
    key: "subscribe",
    value: function (query
    /*: Object*/
    , sessionToken
    /*: ?string*/
    )
    /*: LiveQuerySubscription*/
    {
      if (!query) {
        return;
      }

      var className = query.className;
      var queryJSON = query.toJSON();
      var where = queryJSON.where;
      var fields = (0, _keys.default)(queryJSON) ? (0, _keys.default)(queryJSON).split(',') : undefined;
      var subscribeRequest = {
        op: OP_TYPES.SUBSCRIBE,
        requestId: this.requestId,
        query: {
          className: className,
          where: where,
          fields: fields
        }
      };

      if (sessionToken) {
        subscribeRequest.sessionToken = sessionToken;
      }

      var subscription = new _LiveQuerySubscription.default(this.requestId, query, sessionToken);
      this.subscriptions.set(this.requestId, subscription);
      this.requestId += 1;
      this.connectPromise.then(function () {
        this.socket.send((0, _stringify.default)(subscribeRequest));
      });
      return subscription;
    }
    /**
     * After calling unsubscribe you'll stop receiving events from the subscription object.
     *
     * @param {object} subscription - subscription you would like to unsubscribe from.
     */
  }

Important changes

v3.4.4:

Start of query.subscribe function:

var _this2 = this;

just before return(error thrown in v4.5.0)

this.connectPromise.then(function () {
	_this2.socket.send((0, _stringify.default)(subscribeRequest));
});

v3.5.0

start of Query.subscribe function removes _this2 variable

just before return(error thrown in v4.5.0):

this.connectPromise.then(function () {
	this.socket.send((0, _stringify.default)(subscribeRequest));
});

Reason for error:

the v3.5.0 code uses this line:

this.connectPromise.then(function () {
	this.socket.send((0, _stringify.default)(subscribeRequest));
});

Similarly to the Parse.Query.first() issue, it is important to note that the function within the then call will be run with this set to undefined due to strict mode; while _this2 in v3.4.4 is a Parse.Query object.

Due to this variation, while at first it seems to have the same result upon the removal of the _this2 variable in favor of directly using the this value, due to the use of the variable in a seperate scope, the this value resolves to undefined and this is trying to access undefined.socket which (understandably) results in a typeError.

this change was likely made to save memory by the removal of a seemingly unnecessary variable, but due to the global scope of the then function, actually causes an error upon calling.

Solution:

The readdition of the _this2 variable to the top of the subscribe function, and the modification of the code to the code seen in v3.4.4 will resolve the error and return the correct LiveQuerySubscription value.

the corrected code will be (identical to the v3.4.4 code):

{
    key: "subscribe",
    value: function (query
    /*: Object*/
    , sessionToken
    /*: ?string*/
    )
    /*: LiveQuerySubscription*/
    {
      var _this2 = this;

      if (!query) {
        return;
      }

      var className = query.className;
      var queryJSON = query.toJSON();
      var where = queryJSON.where;
      var fields = (0, _keys.default)(queryJSON) ? (0, _keys.default)(queryJSON).split(',') : undefined;
      var subscribeRequest = {
        op: OP_TYPES.SUBSCRIBE,
        requestId: this.requestId,
        query: {
          className: className,
          where: where,
          fields: fields
        }
      };

      if (sessionToken) {
        subscribeRequest.sessionToken = sessionToken;
      }

      var subscription = new _LiveQuerySubscription.default(this.requestId, query, sessionToken);
      this.subscriptions.set(this.requestId, subscription);
      this.requestId += 1;
      this.connectPromise.then(function () {
        _this2.socket.send((0, _stringify.default)(subscribeRequest));
      });
      return subscription;
    }
    /**
     * After calling unsubscribe you'll stop receiving events from the subscription object.
     *
     * @param {object} subscription - subscription you would like to unsubscribe from.
     */

  }

@parseplatformorg
Copy link
Contributor

🎉 This change has been released in version 3.5.1-alpha.2

@mtrezza
Copy link
Member

mtrezza commented Nov 7, 2022

Closing via #1600

@mtrezza mtrezza closed this as completed Nov 7, 2022
@parseplatformorg parseplatformorg added the state:released-alpha Released as alpha version label Nov 7, 2022
@mtrezza
Copy link
Member

mtrezza commented Nov 7, 2022

This should be fixed with https://github.com/parse-community/Parse-SDK-JS/releases/tag/3.5.1-alpha.2, could you try it out and let us know?

@ChrisGrant
Copy link

This should be fixed with https://github.com/parse-community/Parse-SDK-JS/releases/tag/3.5.1-alpha.2, could you try it out and let us know?

It fixed the problem for me. I was on 3.5.0 and was seeing the same issue. Upgrading to 3.5.1-alpha2 makes the problem go away.

@parseplatformorg
Copy link
Contributor

🎉 This change has been released in version 3.5.1-beta.2

@parseplatformorg parseplatformorg added the state:released-beta Released as beta version label Nov 26, 2022
@parseplatformorg
Copy link
Contributor

🎉 This change has been released in version 3.5.1

@parseplatformorg parseplatformorg added the state:released Released as stable version label Nov 26, 2022
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
state:released Released as stable version state:released-alpha Released as alpha version state:released-beta Released as beta version type:bug Impaired feature or lacking behavior that is likely assumed
Projects
None yet
Development

Successfully merging a pull request may close this issue.

5 participants