不要怂,就是干,撸起袖子干!

Commit 7fd629db by Mick Hansen

feat(promises): initial implementation with backwards compat. tests

1 parent 7b798eb0
var util = require("util")
, Promise = require("bluebird")
, EventEmitter = require("events").EventEmitter
, proxyEventKeys = ['success', 'error', 'sql']
, Utils = require('./utils')
var SequelizePromise = function(resolver) {
var self = this;
// Copied from Bluebird, bluebird doesn't like Promise.call(this)
// mhansen wrote and is no fan of this, but sees no other way of making Promises first class while preserving SQL logging capabilities and BC.
this._bitField = 0;
this._fulfillmentHandler0 = void 0;
this._rejectionHandler0 = void 0;
this._promise0 = void 0;
this._receiver0 = void 0;
this._settledValue = void 0;
this._boundTo = void 0;
// Intercept the resolver so we can resolve with emit's
this._resolveFromResolver(function (resolve, reject) {
self.seqResolve = resolve;
self.seqReject = reject;
if (resolver) {
resolver.apply(this, arguments);
}
}.bind(this));
};
util.inherits(SequelizePromise, Promise);
SequelizePromise.prototype.on = function(evt, fct) {
if (evt === 'success') {
this.then(fct);
}
else if (evt === 'error') {
this.then(null, fct);
}
else {
EventEmitter.prototype.on.call(this, evt, fct);
}
return this;
}
SequelizePromise.prototype.emit = function(evt) {
var args = arguments.length > 1 ? Array.prototype.slice.call(arguments, 1) : [];
if (evt === 'success') {
this.seqResolve.apply(this, args);
} else if (evt === 'error') {
this.seqReject.apply(this, args);
} else {
EventEmitter.prototype.emit.apply(this, [evt].concat(args));
}
return this;
};
/**
* Listen for success events.
*
* ```js
* promise.success(function (result) {
* //...
* });
* ```
*
* @param {function} onSuccess
* @method success
* @alias ok
* @return this
*/
SequelizePromise.prototype.success =
SequelizePromise.prototype.ok = function(fct) {
this.then(fct);
return this;
}
/**
* Listen for error events
*
* ```js
* promise.error(function (err) {
* //...
* });
* ```
*
* @param {function} onError
* @metohd error
* @alias fail
* @alias failure
* @return this
*/
SequelizePromise.prototype.failure =
SequelizePromise.prototype.fail =
SequelizePromise.prototype.error = function(fct) {
this.then(null, fct);
return this;
}
/**
* Listen for both success and error events.
*
* ```js
* promise.done(function (err, result) {
* //...
* });
* ```
*
* @param {function} onDone
* @method done
* @alias complete
* @return this
*/
SequelizePromise.prototype.done =
SequelizePromise.prototype.complete = function(fct) {
if (fct.length > 2) {
this.spread(function () {
fct.apply(null, [null].concat(Array.prototype.slice.call(arguments)));
}, fct);
} else {
this.then(function () {
fct.apply(null, [null].concat(Array.prototype.slice.call(arguments)));
}, fct);
}
return this;
};
/*
* Attach a function that is called every time the function that created this emitter executes a query.
* @param {function} onSQL
* @return this
*/
SequelizePromise.prototype.sql = function(fct) {
this.on('sql', fct)
return this;
}
/**
* Proxy every event of this promise to another one.
*
* @param {SequelizePromise} The promise that should receive the events.
* @param {Object} [options]
* @param {Array} [options.events] An array of the events to proxy. Defaults to sql, error and success
* @return this
*/
SequelizePromise.prototype.proxy = function(promise, options) {
options = Utils._.extend({
events: proxyEventKeys,
skipEvents: []
}, options || {})
options.events = Utils._.difference(options.events, options.skipEvents)
options.events.forEach(function (eventKey) {
this.on(eventKey, function () {
var args = [ eventKey ].concat([].slice.apply(arguments))
promise.emit.apply(promise, args)
})
}.bind(this))
return this
}
module.exports = SequelizePromise;
\ No newline at end of file
......@@ -10,6 +10,7 @@ var url = require("url")
, TransactionManager = require('./transaction-manager')
, QueryTypes = require('./query-types')
, sequelizeErrors = require('./errors')
, Promise = require('./promise')
/**
* This is the main class, the entry point to sequelize. To use it, you just need to import sequelize:
......
......@@ -2,8 +2,11 @@ var chai = require('chai')
, expect = chai.expect
, Support = require(__dirname + '/support')
, DataTypes = require(__dirname + "/../lib/data-types")
, SequelizePromise = require(__dirname + "/../lib/promise")
, Promise = require('bluebird')
, dialect = Support.getTestDialect()
, _ = require('lodash')
, sinon = require('sinon')
chai.config.includeStack = true
......@@ -358,4 +361,232 @@ describe(Support.getTestDialectTeaser("Promise"), function () {
})
})
})
})
describe('backwards compat', function () {
it('should still work with .complete() when resolving', function(done) {
var spy = sinon.spy()
, promise = new SequelizePromise(function (resolve, reject) {
resolve('abc');
});
promise.complete(spy);
promise.then(function () {
expect(spy.calledOnce).to.be.true
expect(spy.firstCall.args).to.deep.equal([null, 'abc']);
done()
});
});
it('should still work with .success() when resolving', function(done) {
var spy = sinon.spy()
, promise = new SequelizePromise(function (resolve, reject) {
resolve('yay');
});
promise.success(spy);
promise.then(function () {
expect(spy.calledOnce).to.be.true
expect(spy.firstCall.args).to.deep.equal(['yay']);
done()
});
});
it('should still work with .on(\'success\') when resolving', function(done) {
var spy = sinon.spy()
, promise = new SequelizePromise(function (resolve, reject) {
resolve('yoohoo');
});
promise.on('success', spy);
promise.then(function () {
expect(spy.calledOnce).to.be.true
expect(spy.firstCall.args).to.deep.equal(['yoohoo']);
done()
});
});
it('should still work with .done() when resolving multiple results', function(done) {
var spy = sinon.spy()
, promise = new SequelizePromise(function (resolve, reject) {
resolve(Promise.all(['MyModel', true]));
});
promise.spread(spy);
promise.done(function (err, model, created) {
expect(model).to.equal('MyModel')
expect(created).to.be.true
expect(spy.calledOnce).to.be.true
expect(spy.firstCall.args).to.deep.equal(['MyModel', true]);
done()
});
});
it('should still work with .success() when emitting', function(done) {
var spy = sinon.spy()
, promise = new SequelizePromise(function (resolve, reject) {
// no-op
});
promise.success(spy);
promise.then(function () {
expect(spy.calledOnce).to.be.true
expect(spy.firstCall.args).to.deep.equal(['yay']);
done()
});
promise.emit('success', 'yay');
});
it.only('should still work with .done() when rejecting', function(done) {
var spy = sinon.spy()
, promise = new SequelizePromise(function (resolve, reject) {
reject(new Error('no'));
});
promise.done(spy);
promise.catch(function () {
expect(spy.calledOnce).to.be.true
expect(spy.firstCall.args[0]).to.be.an.instanceof(Error)
done()
});
});
it('should still work with .error() when throwing', function(done) {
var spy = sinon.spy()
, promise = new SequelizePromise(function (resolve, reject) {
throw new Error('no');
});
promise.error(spy);
promise.catch(function () {
expect(spy.calledOnce).to.be.true
expect(spy.firstCall.args[0]).to.be.an.instanceof(Error)
done()
});
});
it('should still work with .on(\'error\') when throwing', function(done) {
var spy = sinon.spy()
, promise = new SequelizePromise(function (resolve, reject) {
throw new Error('noway');
});
promise.on('error', spy);
promise.catch(function () {
expect(spy.calledOnce).to.be.true
expect(spy.firstCall.args[0]).to.be.an.instanceof(Error)
done()
});
});
it('should still work with .error() when emitting', function(done) {
var spy = sinon.spy()
, promise = new SequelizePromise(function (resolve, reject) {
// no-op
});
promise.on('error', spy);
promise.catch(function () {
expect(spy.calledOnce).to.be.true
expect(spy.firstCall.args[0]).to.be.an.instanceof(Error)
done()
});
promise.emit('error', new Error('noway'));
});
it('should still support sql events', function (done) {
var spy = sinon.spy()
, promise = new SequelizePromise(function (resolve, reject) {
resolve('yay');
});
promise.on('sql', spy);
promise.emit('sql', 'SQL STATEMENT 1');
promise.emit('sql', 'SQL STATEMENT 2');
promise.then(function () {
expect(spy.calledTwice).to.be.true;
done();
});
});
describe("proxy", function () {
it("should correctly work with success listeners", function(done) {
var emitter = new SequelizePromise()
, proxy = new SequelizePromise()
, success = sinon.spy()
emitter.success(success)
proxy.success(function () {
process.nextTick(function () {
expect(success.called).to.be.true
done()
})
})
proxy.proxy(emitter)
proxy.emit('success')
})
it("should correctly work with error listeners", function(done) {
var emitter = new SequelizePromise()
, proxy = new SequelizePromise()
, error = sinon.spy()
emitter.error(error)
proxy.error(function() {
process.nextTick(function() {
expect(error.called).to.be.true
expect(error.firstCall.args[0]).to.be.an.instanceof(Error)
done()
})
})
proxy.proxy(emitter)
proxy.emit('error', new Error('reason'))
})
it("should correctly work with complete/done listeners", function(done) {
var promise = new SequelizePromise()
, proxy = new SequelizePromise()
, complete = sinon.spy()
promise.complete(complete)
proxy.complete(function() {
process.nextTick(function() {
expect(complete.called).to.be.true
done()
})
})
proxy.proxy(promise)
proxy.emit('success')
})
})
describe("when emitting an error event with an array of errors", function() {
describe("if an error handler is given", function() {
it("should return the whole array", function(done) {
var emitter = new SequelizePromise()
var errors = [
[
new Error("First error"),
new Error("Second error")
], [
new Error("Third error")
]
]
emitter.error(function (err) {
expect(err).to.equal(errors)
done()
})
emitter.emit("error", errors)
})
})
})
});
});
\ No newline at end of file
Markdown is supported
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!