Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Can I use other promise implementations in the Parse JavaScript SDK?

I am concerned about references I have seen to Parse using JQuery-compatible promises, as I have read that jQuery promises allow consumers to mutate the state of the promise. Is it possible to use another promise implementation that is known to be Promises/A+ compliant (e.g. the ECMAScript 6 implementation, or Bluebird) with the Parse JavaScript SDK?

Normally I would assume that this isn’t possible, but in v1.4.2 of the Parse JavaScript SDK, the implementation of Parse.Promise defines the property “_isPromisesAPlusCompliant” as false which is then checked in various functions within the library.

N.B. This question was originally asked on the Parse Developers group, but received no responses.

like image 824
Jason Whittle Avatar asked Jun 17 '15 16:06

Jason Whittle


People also ask

What is Parse SDK?

What is Parse? Parse is an open-source Android SDK and back-end solution that enables developers to build mobile apps with shared data quickly and without writing any back-end code or custom APIs. Parse is a Node.

What database does parse use?

Parse Server uses MongoDB or PostgreSQL as a database. You can deploy and run Parse Server on your own infrastructure. You can develop and test your app locally using Node.

How do I use promises in node JS?

A Promise in Node means an action which will either be completed or rejected. In case of completion, the promise is kept and otherwise, the promise is broken. So as the word suggests either the promise is kept or it is broken. And unlike callbacks, promises can be chained.

What is parse server used for?

The sole purpose of Parse was to demystify the process of backend development. Launched in February 2016, Parse Server is an open source version of Parse (MBaaS platform) which was originally developed by Parse Inc. It can be deployed to any infrastructure that can run node. js.


3 Answers

You can use native Promises, or a good polyfill. You can encapsulate any thenable (a Promise-like object with a public then method) in Promise.resolve call, like this:

var promise = Promise.resolve($.getJSON("/something.json"));

This will also have a then method, but without any headaches. It should still work.

like image 30
frontsideair Avatar answered Nov 02 '22 01:11

frontsideair


I am concerned that Parse uses jQuery-compatible promises, as I have read that jQuery promises allow consumers to mutate the state of the promise.

You don't need to be concerned. "jQuery-compatible" can mean a lot of things, and Parse promises do certainly not allow consumers to mutate their state1 (as jQuery doesn't do either since years now). Btw, they're A+ "compatible" as well :-)

1: through the public methods. So not more than most other implementations, that is.

Is it possible to use another promise implementation that is known to be Promises/A+ compliant with the Parse JavaScript SDK?

Yes. The Parse SDK does return valid A+ thenables, which means that you can return Parse promises from then callbacks of your favourite promise implementation and expect it to work flawlessly:

myCompliantPromise.then(function(res) {
    return parse_query.get(…);
}).then(…)

You can also cast them into valid promises of your implementation by using Promise.resolve, for example:

Promise.resolve(parse_query.get(…)).then(…);

Normally I would assume that this isn’t possible, but in v1.4.2 of the Parse JavaScript SDK, the implementation of Parse.Promise defines the property _isPromisesAPlusCompliant as false which is then checked in various functions within the library.

He! Although it is unfortunately undocumented, this flag does actually allow you to make the native Parse.com promise library A+ compliant in your app:

Parse.Promise._isPromisesAPlusCompliant = true;

Update: In newer versions, this is not exposed as an underscored property, but rather you have to call the (undocumented) Parse.Promise.enableAPlusCompliant() method. For details see issue #57.

I've reviewed the code, and this flag basically changes 3 things:

  • Exceptions in then callbacks are caught and lead to the rejection of the result promise, instead of a global error. So you can use throw in them.
  • If you return a value from the onRejected callback (second parameter to then), the error is supposed to be handled and the result promise is fulfilled instead of being rejected.
  • All then callbacks are executed asynchronously.

These are indeed solving exactly the problems inherent to the jQuery Deferred implementation at the current time.

I'll assume that Parse are planning to silently migrate this true setting to become the default, and are testing whether it breaks anything for the users. I'd guess that it is pretty safe to use even if undocumented yet.

I'd like to make all Parse APIs return promises of my custom library.

That's not so simple, although it can be done. There are basically two approaches:

  • decorate all promise-returning methods in the API by composing them with Promise.resolve, which is basically what @dancamper suggested
  • overwriting Parse.Promise with a wrapper around your library.

The second seems to be more efficient and stable, it's more maintainable as it doesn't require tweaking when Parse change their API.

Parse.Promise = (function(oldPromise, Promise) {
    function promise() {
        var res, rej;
        var p = new Promise(function(_res, _rej) {
            res = _res;
            rej = _rej;
        });
        p.resolve = res;
        p.reject = rej;
        return p;
    }
    promise.is = oldPromise.is;
    promise.as = Promise.resolve;
    promise.error = Promise.reject;
    promise.when = Promise.all; // ²
    promise._continueWhile = oldPromise._continueWhile;
    Promise.prototype._continueWith = oldPromise.prototype._continueWith;
    Promise.prototype._thenRunCallback = oldPromise.prototype._thenRunCallback;

    // you might not need / want these ³
    Promise.prototype.always = oldPromise.prototype.always;
    Promise.prototype.done = oldPromise.prototype.done; 
    Promise.prototype.fail = oldPromise.prototype.fail;

    return promise;
}(Parse.Promise, require("Bluebird"))); // or whatever

2: Promise.all resolves to an array, while Parse.Promise.when resolves with multiple arguments (see below). You may want / need to preserve this and use promise.when = oldPromise.when; instead.
3: Make sure not to overwrite methods of your custom library here. Parse doesn't need these methods, they're for jQuery compatibility.

Notice that Parse does, like jQuery, sometimes resolve its promises with multiple values, e.g. in Parse._ajax. It doesn't rely on this feature internally, but you should check how your favourite promise library copes with them.

like image 189
Bergi Avatar answered Nov 02 '22 00:11

Bergi


One option is to modify the Parse SDK prototypes to return a different type of Promise.

A good starting point is this library https://github.com/brandid/parse-angular-patch/blob/master/src/parse-angular.js which patches the Parse prototypes to return AngularJS promises

            // Keep a handy local reference
            var Parse = $window.Parse;

            //-------------------------------------
            // Structured object of what we need to update
            //-------------------------------------

            var methodsToUpdate = {
                "Object": {
                    prototype: ['save', 'fetch', 'destroy'],
                    static: ['saveAll', 'destroyAll']
                },
                "Collection": {
                    prototype: ['fetch'],
                    static: []
                },
                "Query": {
                    prototype: ['find', 'first', 'count', 'get'],
                    static: []
                },
                "Cloud": {
                    prototype: [],
                    static: ['run']
                },
                "User": {
                    prototype: ['signUp'],
                    static: ['requestPasswordReset', 'logIn']
                },
                "FacebookUtils": {
                    prototype: [],
                    static: ['logIn', 'link', 'unlink']
                },
                "Config": {
                    prototype: [],
                    static: ['get']
                }
            };

            //// Let's loop over Parse objects
            for (var k in methodsToUpdate) {

                var currentClass = k;
                var currentObject = methodsToUpdate[k];

                var currentProtoMethods = currentObject.prototype;
                var currentStaticMethods = currentObject.static;


                /// Patching prototypes
                currentProtoMethods.forEach(function(method){

                    var origMethod = Parse[currentClass].prototype[method];

                    // Overwrite original function by wrapping it with $q
                    Parse[currentClass].prototype[method] = function() {

                        return origMethod.apply(this, arguments)
                        .then(function(data){
                            var defer = $q.defer();
                            defer.resolve(data);
                            return defer.promise;
                        }, function(err){
                            var defer = $q.defer();
                            defer.reject(err);
                            return defer.promise;
                        });


                    };

                });


                ///Patching static methods too
                currentStaticMethods.forEach(function(method){

                    var origMethod = Parse[currentClass][method];

                    // Overwrite original function by wrapping it with $q
                    Parse[currentClass][method] = function() {

                        return origMethod.apply(this, arguments)
                        .then(function(data){
                            var defer = $q.defer();
                            defer.resolve(data);
                            return defer.promise;
                        }, function(err){
                            var defer = $q.defer();
                            defer.reject(err);
                            return defer.promise;
                        });

                    };

                });


            }
like image 3
dancampers Avatar answered Nov 02 '22 00:11

dancampers