Search code examples
javascriptpromisebluebirdodm

Implementing (Inheriting) Promise interface in an ORM


I want to promisify an ODM/ORM. How would you implement promise interface while having other methods such as find(), insert(), update() etc. So you could do

var Users = Collection('users')
Users.find({name: 'joe'})
  .then(users => users.update({name: 'jim'))
  .then(console.log)

I'm thinking of inheriting from Promise but how do you customize the then() to return your ORM instance to ensure we're working with the instance with all the queries made in sequence. Right now I'm using composition but then I have to proxy every method call which is getting ugly.

Eg:

var Promise = require('bluebird')

var Collection = function(Storage, name) {
    this.promise = new Promise(function(resolve, reject) {
            self.resolve = resolve
            self.reject = reject
        })
}

Collection.prototype.find = function(query) {
    // async query stuff here
    Storage.doAsyncQueryStuff(function (err, results) {
        err && this.reject(err)
        this.resolve(results)
    })
}

Collection.prototype.then = function(callback) {
    var self = this
    this.promise.then(function() {
        callback && callback.apply(self, arguments)
    })
    return this
}

What I'm trying to do is:

var inherits = require('util').inherits
var Promise = require('bluebird')

var Collection = function(Storage, name) {
    var self = this
    // error here
    Promise.call(
        this,
        function(resolve, reject) {
            self.resolve = resolve
            self.reject = reject
        })
}
inherits(Collection, Promise)

I can't seem to get Promise to be initialized. Or should I be doing this a different way?


Solution

  • After a bit of research I find that inheriting a promise in an ORM Instance is not a good idea because of the following:

    Promises resolve recursively, each returning a new promise resolved from the last.

    eg:

    var debug = console.log
    
    var promise1 = new Promise((r, e) => {
        r(1)
    })
    var promise2 = new Promise((r, e) => {
        r(2)
    })
    
    var promise3 = promise1.then(data => promise2)
    
    debug('promise1 === promise1', promise1 === promise1) // true
    debug('promise3 === promise1', promise3 === promise1) // false
    debug('promise3 === promise2', promise3 === promise2) // false
    

    promise1 will first resolve promise2 and use the resolved value as the resolution value of promise1. promise1 will then return a new promise (promise3) resolved to the value of promise1.

    This is important because promise resolution states are immutable yet promises are chainable.

    To efficiently return a new promise for each then() call in an ODM while retaining it's current state would require optimizations such as making the ORM state data immutable or referenced from a global store (registry) while keeping it's current query unique (the filters, sorts, loaded relationships) etc. It's simpler to just compose the promise interface like then(), catch() onto the ORM as the Promise specification is quite forgiving.

    Extending bluebird by inheritence is prevented since there is a check to only allow instance creation by an Object of type Promise. https://github.com/petkaantonov/bluebird/issues/325

    Bluebird as well as the built in Promise in FF, Chrome and Node.js can't be extended via ES5 inheritence. They all require and instance of Promise to instantiate.

    ECMA262 defines the Promise object inheritable. http://www.ecma-international.org/ecma-262/6.0/#sec-promise-constructor

    Using ES6 class syntax you can extend the built in browser promise.

    class MyPromise extends Promise {} 
    

    This may work on Node.js using Babel.

    You can extend both bluebird and the built-in promise by setting the __proto__ property directly in ES5.

    /**
     * @class   MyPromise
     * @extends Promise
     */
    var MyPromise = function () {
      var resolve, reject
      var promise = new Promise(function(_resolve, _reject) {
          resolve = _resolve
          reject = _reject
        })
      promise.__proto__ = this.__proto__
      promise.resolve = resolve
      promise.reject = reject
      return promise
    }
    
    MyPromise.prototype = Object.create(Promise.prototype, { constructor: { value: MyPromise } })
    
    MyPromise.prototype.call = function() {
        this.resolve(new Date)
    }
    
    MyPromise.all = Promise.all
    MyPromise.cast = Promise.cast
    MyPromise.reject = Promise.reject
    MyPromise.resolve = Promise.resolve
    
    module.exports = MyPromise
    

    Though I wouldn't recommend it as __proto__ is not standard though supported widely.