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?
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.