Merge branch 'bluebird'

* bluebird:
  cleanup on the .then interface
  swapping in bluebird, some promise cleanup
This commit is contained in:
Tim Griesser 2013-11-02 09:24:17 -04:00
commit 17d09f2655
20 changed files with 139 additions and 127 deletions

View File

@ -6,9 +6,7 @@
define(function(require, exports) {
// All of the "when.js" promise components needed in this module.
var when = require('when');
var nodefn = require('when/node/function');
var Promise = require('../lib/promise').Promise;
var _ = require('underscore');
var GenericPool = require('generic-pool-redux').Pool;
@ -43,10 +41,10 @@ define(function(require, exports) {
.tap(function(connection) {
connection.__cid = _.uniqueId('__cid');
if (pool.config.afterCreate) {
return nodefn.call(pool.config.afterCreate, connection);
return Promise.promisify(pool.config.afterCreate)(connection);
}
});
return nodefn.bindCallback(promise, callback);
return promise.nodeify(callback);
},
destroy: function(connection) {
if (pool.config.beforeDestroy) {

View File

@ -1,15 +1,11 @@
// ServerBase
// -------
// All of the "when.js" promise components needed in this module.
var when = require('when');
var nodefn = require('when/node/function');
var sequence = require('when/sequence');
var _ = require('underscore');
var Pool = require('../pool').Pool;
var ClientBase = require('../base').ClientBase;
var Promise = require('../../lib/promise').Promise;
var ServerBase = ClientBase.extend({
@ -43,10 +39,14 @@ var ServerBase = ClientBase.extend({
}
conn = connection;
if (_.isArray(sql)) {
return sequence(sql.map(function(query, i) {
builder.currentIndex = i;
return function() { return client.runQuery(connection, query, bindings, builder); };
}));
var current = Promise.fulfilled();
return Promise.map(sql, function(query, i) {
current = current.then(function () {
builder.currentIndex = i;
return client.runQuery(connection, query, bindings, builder);
});
return current;
});
}
return client.runQuery(connection, sql, bindings, builder);
});
@ -69,7 +69,7 @@ var ServerBase = ClientBase.extend({
// into a new error... this way, it `console.log`'s nicely for debugging, but you can also
// parse them out with a `JSON.parse(error.message)`. Also, use the original `clientError` from the
// database client is retained as a property on the `newError`, for any additional info.
return chain.then(builder.handleResponse).otherwise(function(error) {
return chain.then(builder.handleResponse).caught(function(error) {
var newError = new Error(error.message + ', sql: ' + sql + ', bindings: ' + bindings);
newError.sql = sql;
newError.bindings = bindings;
@ -86,14 +86,14 @@ var ServerBase = ClientBase.extend({
// Retrieves a connection from the connection pool,
// returning a promise.
getConnection: function(builder) {
if (builder && builder.usingConnection) return when(builder.usingConnection);
return nodefn.call(this.pool.acquire);
if (builder && builder.usingConnection) return Promise.fulfilled(builder.usingConnection);
return Promise.promisify(this.pool.acquire, this.pool)();
},
// Releases a connection from the connection pool,
// returning a promise.
releaseConnection: function(conn) {
return nodefn.call(this.pool.release, conn);
return Promise.promisify(this.pool.release)(conn);
},
// Begins a transaction statement on the instance,
@ -101,7 +101,7 @@ var ServerBase = ClientBase.extend({
startTransaction: function() {
return this.getConnection()
.tap(function(connection) {
return nodefn.call(connection.query.bind(connection), 'begin;', []);
return Promise.promisify(connection.query, connection)('begin;', []);
});
},
@ -109,8 +109,8 @@ var ServerBase = ClientBase.extend({
finishTransaction: function(type, transaction, msg) {
var client = this;
var dfd = transaction.dfd;
nodefn.call(transaction.connection.query.bind(transaction.connection), type + ';', []).then(function(resp) {
if (type === 'commit') dfd.resolve(msg || resp);
Promise.promisify(transaction.connection.query, transaction.connection)(type + ';', []).then(function(resp) {
if (type === 'commit') dfd.fulfill(msg || resp);
if (type === 'rollback') dfd.reject(msg || resp);
}, function (err) {
dfd.reject(err);

View File

@ -1,10 +1,6 @@
// MySQL
// -------
// All of the "when.js" promise components needed in this module.
var when = require('when');
var nodefn = require('when/node/function');
// Other dependencies, including the `mysql` library,
// which needs to be added as a dependency to the project
// using this database.
@ -14,6 +10,7 @@ var mysql = require('mysql');
// All other local project modules needed in this scope.
var ServerBase = require('./base').ServerBase;
var Helpers = require('../../lib/helpers').Helpers;
var Promise = require('../../lib/promise').Promise;
var grammar = require('./mysql/grammar').grammar;
var schemaGrammar = require('./mysql/schemagrammar').schemaGrammar;
@ -32,21 +29,23 @@ exports.Client = ServerBase.extend({
// Runs the query on the specified connection, providing the bindings
// and any other necessary prep work.
runQuery: function(connection, sql, bindings, builder) {
if (!connection) throw new Error('No database connection exists for the query');
if (builder.flags.options) sql = _.extend({sql: sql}, builder.flags.options);
if (builder._source === 'SchemaBuilder') {
sql = this.advancedQuery(connection, sql, bindings, builder);
}
return when(sql).then(function(sql) {
return nodefn.call(connection.query.bind(connection), sql, bindings);
});
return Promise.bind(this).then(function() {
if (!connection) throw new Error('No database connection exists for the query');
if (builder.flags.options) sql = _.extend({sql: sql}, builder.flags.options);
if (builder._source === 'SchemaBuilder') {
return this.advancedQuery(connection, sql, builder);
}
return sql;
}).then(function(sql) {
return Promise.promisify(connection.query, connection)(sql, bindings);
}).bind();
},
// Get a raw connection, called by the `pool` whenever a new
// connection needs to be added to the pool.
getRawConnection: function() {
var connection = mysql.createConnection(this.connectionSettings);
return nodefn.call(connection.connect.bind(connection)).yield(connection);
return Promise.promisify(connection.connect, connection)().yield(connection);
},
// Used to explicitly close a connection, called internally by the pool
@ -56,10 +55,10 @@ exports.Client = ServerBase.extend({
},
// Used to check if there is a conditional query needed to complete the next one.
advancedQuery: function(connection, sql, bindings, builder) {
advancedQuery: function(connection, sql, builder) {
if (sql.indexOf('alter table') === 0 && sql.indexOf('__datatype__') === (sql.length - 12)) {
var newSql = sql.replace('alter table', 'show fields from').split('change')[0] + ' where field = ?';
return nodefn.call(connection.query.bind(connection), newSql, [builder.commands[builder.currentIndex].from]).then(function(resp) {
return Promise.promisify(connection.query, connection)(newSql, [builder.commands[builder.currentIndex].from]).then(function(resp) {
var column = resp[0];
// Set to the datatype we're looking to change it to...
return sql.replace('__datatype__', column[0].Type);

View File

@ -1,10 +1,6 @@
// PostgreSQL
// -------
// All of the "when.js" promise components needed in this module.
var when = require('when');
var nodefn = require('when/node/function');
// Other dependencies, including the `pg` library,
// which needs to be added as a dependency to the project
// using this database.
@ -14,6 +10,7 @@ var pg = require('pg');
// All other local project modules needed in this scope.
var ServerBase = require('./base').ServerBase;
var Helpers = require('../../lib/helpers').Helpers;
var Promise = require('../../lib/promise').Promise;
var grammar = require('./postgres/grammar').grammar;
var schemaGrammar = require('./postgres/schemagrammar').schemaGrammar;
@ -39,17 +36,16 @@ exports.Client = ServerBase.extend({
return '$' + questionCount;
});
if (builder && builder.flags.options) sql = _.extend({text: sql}, builder.flags.options);
return nodefn.call(connection.query.bind(connection), sql, bindings);
return Promise.promisify(connection.query, connection)(sql, bindings);
},
// Get a raw connection, called by the `pool` whenever a new
// connection needs to be added to the pool.
getRawConnection: function(callback) {
var instance = this;
var connection = new pg.Client(this.connectionSettings);
return nodefn.call(connection.connect.bind(connection)).tap(function() {
if (!instance.version) return instance.checkVersion(connection);
}).yield(connection);
return Promise.promisify(connection.connect, connection)().bind(this).tap(function() {
if (!this.version) return this.checkVersion(connection);
}).bind().yield(connection);
},
// Used to explicitly close a connection, called internally by the pool

View File

@ -1,10 +1,6 @@
// SQLite3
// -------
// All of the "when.js" promise components needed in this module.
var when = require('when');
var nodefn = require('when/node/function');
// Other dependencies, including the `sqlite3` library,
// which needs to be added as a dependency to the project
// using this database.
@ -17,6 +13,7 @@ var Builder = require('../../lib/builder').Builder;
var Transaction = require('../../lib/transaction').Transaction;
var SchemaInterface = require('../../lib/schemainterface').SchemaInterface;
var Helpers = require('../../lib/helpers').Helpers;
var Promise = require('../../lib/promise').Promise;
var grammar = require('./sqlite3/grammar').grammar;
var schemaGrammar = require('./sqlite3/schemagrammar').schemaGrammar;
@ -42,11 +39,11 @@ var SQLite3Client = exports.Client = ServerBase.extend({
var method = (builder.type === 'insert' ||
builder.type === 'update' || builder.type === 'delete') ? 'run' : 'all';
// Call the querystring and then release the client
var dfd = when.defer();
var dfd = Promise.pending();
connection[method](sql, bindings, function(err, resp) {
if (err) return dfd.reject(err);
// We need the context here, because it has the "this.lastID" or "this.changes"
return dfd.resolve([resp, this]);
return dfd.fulfill([resp, this]);
});
return dfd.promise;
},
@ -59,7 +56,7 @@ var SQLite3Client = exports.Client = ServerBase.extend({
ddl: function(connection, sql, bindings, builder) {
var client = this;
return nodefn.call(connection.run.bind(connection), 'begin transaction;').then(function() {
return Promise.promisify(connection.run, connection)('begin transaction;').then(function() {
var transaction = new Transaction({client: client});
var containerObj = transaction.getContainerObject(connection);
return transaction.initiateDeferred(function(trx) {
@ -69,10 +66,10 @@ var SQLite3Client = exports.Client = ServerBase.extend({
},
getRawConnection: function() {
var dfd = when.defer();
var dfd = Promise.pending();
var db = new sqlite3.Database(this.connectionSettings.filename, function(err) {
if (err) return dfd.reject(err);
dfd.resolve(db);
dfd.fulfill(db);
});
return dfd.promise;
},
@ -87,7 +84,7 @@ var SQLite3Client = exports.Client = ServerBase.extend({
// resolving with the connection of the current transaction.
startTransaction: function(connection) {
return this.getConnection().tap(function(connection) {
return nodefn.call(connection.run.bind(connection), 'begin transaction;', []);
return Promise.promisify(connection.run, connection)('begin transaction;');
});
},
@ -95,26 +92,29 @@ var SQLite3Client = exports.Client = ServerBase.extend({
finishTransaction: function(type, transaction, msg) {
var client = this;
var dfd = transaction.dfd;
nodefn.call(transaction.connection.run.bind(transaction.connection), type + ';', []).then(function(resp) {
if (type === 'commit') dfd.resolve(msg || resp);
if (type === 'rollback') dfd.reject(msg || resp);
}, function (err) {
dfd.reject(err);
}).ensure(function() {
return client.releaseConnection(transaction.connection).tap(function() {
transaction.connection = null;
return Promise
.promisify(transaction.connection.run, transaction.connection)(type + ';')
.then(function(resp) {
if (type === 'commit') dfd.fulfill(msg || resp);
if (type === 'rollback') dfd.reject(msg || resp);
}, function (err) {
dfd.reject(err);
}).ensure(function() {
return client.releaseConnection(transaction.connection).tap(function() {
transaction.connection = null;
});
});
});
},
// This needs to be refactored... badly.
alterSchema: function(builder, trx) {
var connection = trx.connection;
var currentCol, command;
var connection = trx.connection;
var query = Promise.promisify(connection.all, connection);
return when.all([
nodefn.call(connection.all.bind(connection), 'PRAGMA table_info(' + builder.table + ')', []),
nodefn.call(connection.all.bind(connection), 'SELECT name, sql FROM sqlite_master WHERE type="table" AND name="' + builder.table + '"', [])
return Promise.all([
query('PRAGMA table_info(' + builder.table + ')', []),
query('SELECT name, sql FROM sqlite_master WHERE type="table" AND name="' + builder.table + '"', [])
])
.tap(function(resp) {
var pragma = resp[0];
@ -123,7 +123,7 @@ var SQLite3Client = exports.Client = ServerBase.extend({
if (!(currentCol = _.findWhere(pragma, {name: command.from}))) {
throw new Error('The column ' + command.from + ' is not in the current table');
}
return nodefn.call(connection.all.bind(connection), 'ALTER TABLE ' + sql.name + ' RENAME TO __migrate__' + sql.name);
return query('ALTER TABLE ' + sql.name + ' RENAME TO __migrate__' + sql.name);
}).spread(function(pragma, sql) {
sql = sql[0];
var currentColumn = '"' + command.from + '" ' + currentCol.type;
@ -131,19 +131,19 @@ var SQLite3Client = exports.Client = ServerBase.extend({
if (sql.sql.indexOf(currentColumn) === -1) {
return trx.reject('Unable to find the column to change');
}
return when.all([
nodefn.call(connection.all.bind(connection), sql.sql.replace(currentColumn, newColumn)),
nodefn.call(connection.all.bind(connection), 'SELECT * FROM "__migrate__' + sql.name + '"'),
return Promise.all([
query(sql.sql.replace(currentColumn, newColumn)),
query('SELECT * FROM "__migrate__' + sql.name + '"'),
]);
}).spread(function(createTable, selected) {
var qb = new Builder(builder.knex).transacting(trx);
qb.table = builder.table;
return when.all([
return Promise.all([
qb.insert(_.map(selected, function(row) {
row[command.to] = row[command.from];
return _.omit(row, command.from);
})),
nodefn.call(connection.all.bind(connection), 'DROP TABLE "__migrate__' + builder.table + '"')
query('DROP TABLE "__migrate__' + builder.table + '"')
]);
}).then(trx.commit, trx.rollback);
}

View File

@ -13,7 +13,6 @@ define(function(require, exports, module) {
// Base library dependencies of the app.
var _ = require('underscore');
var when = require('when');
// Require the main constructors necessary for a `Knex` instance,
// each of which are injected with the current instance, so they maintain

View File

@ -44,26 +44,29 @@ define(function(require, exports) {
// if called more than once. Any unhandled errors will be thrown
// after the last block.
exec: function(callback) {
this._promise || (this._promise = this.client.query(this));
return this._promise.then(function(resp) {
return this.then(function(resp) {
if (callback) callback(null, resp);
}, function(err) {
if (callback) callback(err, null);
}).otherwise(function(err) {
}).caught(function(err) {
setTimeout(function() { throw err; }, 0);
});
},
// The promise interface for the query builder.
then: function(onFulfilled, onRejected) {
this._promise || (this._promise = this.client.query(this));
if (!this._promise) {
this._promise = Promise.bind(this);
this._promise = this._promise.then(function() {
return this.client.query(this);
}).bind();
}
return this._promise.then(onFulfilled, onRejected);
},
// Passthrough to the convenient `tap` mechanism of when.js
tap: function(handler) {
this._promise = this._promise || this.client.query(this);
return this._promise.tap(handler);
return this.then().tap(handler);
},
// Returns an array of query strings filled out with the

View File

@ -1,13 +0,0 @@
// Error
// -------
(function(define) {
"use strict";
define(function(require, exports) {
});
})(
typeof define === 'function' && define.amd ? define : function (factory) { factory(require, exports); }
);

31
lib/promise.js Normal file
View File

@ -0,0 +1,31 @@
(function(define) {
"use strict";
define(function(require, exports) {
var Promise = require('bluebird/js/main/promise')();
Promise.prototype.yield = function(value) {
return this.then(function() {
return value;
});
};
Promise.prototype.tap = function(handler) {
return this.then(handler).yield(this);
};
Promise.prototype.ensure = Promise.prototype.lastly;
Promise.prototype.otherwise = Promise.prototype.caught;
Promise.resolve = Promise.fulfilled;
Promise.reject = Promise.rejected;
exports.Promise = Promise;
});
})(
typeof define === 'function' && define.amd ? define : function (factory) { factory(require, exports); }
);

View File

@ -6,8 +6,8 @@
define(function(require, exports) {
var when = require('when');
var _ = require('underscore');
var Promise = require('./promise').Promise;
var _ = require('underscore');
// Creates a new wrapper object for constructing a transaction.
// Called by the `knex.transaction`, which sets the correct client
@ -15,7 +15,6 @@ define(function(require, exports) {
// `connection` to keep all of the transactions on the correct connection.
var Transaction = function(instance) {
this.client = instance.client;
_.bindAll(this, 'getContainerObject');
};
Transaction.prototype = {
@ -24,8 +23,10 @@ define(function(require, exports) {
// transaction, returning a promise.
run: function(container, connection) {
return this.client.startTransaction(connection)
.bind(this)
.then(this.getContainerObject)
.then(this.initiateDeferred(container));
.then(this.initiateDeferred(container))
.bind();
},
getContainerObject: function(connection) {
@ -60,7 +61,7 @@ define(function(require, exports) {
// Initiate a deferred object, so we know when the
// transaction completes or fails, we know what to do.
var dfd = containerObj.dfd = when.defer();
var dfd = containerObj.dfd = Promise.pending();
// Call the container with the transaction
// commit & rollback objects.

View File

@ -22,9 +22,9 @@
"sinon-chai": "~2.4.0"
},
"dependencies": {
"when": "~2.4.0",
"underscore": "~1.5.1",
"generic-pool-redux": "~0.1.0"
"generic-pool-redux": "~0.1.0",
"bluebird": "~0.9.5-0"
},
"scripts": {
"test": "mocha -R spec test/index.js",

View File

@ -1,6 +1,4 @@
// Helps with error handling on errors swallowed by promises.
require('when/monitor/console');
var mocha = require('mocha');
require("mocha-as-promised")(mocha);
@ -13,12 +11,15 @@ chai.use(require("chai-as-promised"));
chai.use(require("sinon-chai"));
chai.should();
global.whenResolve = require('when').resolve;
var Promise = global.testPromise = require('../lib/promise').Promise;
global.expect = chai.expect;
global.AssertionError = chai.AssertionError;
global.Assertion = chai.Assertion;
global.assert = chai.assert;
Promise.longStackTraces();
// Unit tests
describe('Unit Tests', function() {
require('./unit/knex');

View File

@ -1,5 +1,3 @@
var when = require("when");
module.exports = function(knex) {
describe('Aggregate', function() {

View File

@ -1,11 +1,11 @@
var when = require('when');
var Promise = testPromise;
module.exports = function(knex) {
describe('Schema', function() {
it('has a dropTableIfExists method', function() {
return when.all([
return Promise.all([
knex.schema.dropTableIfExists('test_foreign_table_two').logMe('sql'),
knex.schema.dropTableIfExists('test_table_one').logMe('sql'),
knex.schema.dropTableIfExists('test_table_two'),

View File

@ -1,4 +1,4 @@
var when = require('when');
var Promise = testPromise;
module.exports = function(knex) {
@ -63,7 +63,7 @@ module.exports = function(knex) {
it('allows where id: undefined or id: null as a where null clause', function() {
return when.all([
return Promise.all([
knex('accounts').logMe('sql').where({'id': void 0}).select('*'),
knex('accounts').logMe('sql').where({'id': null}).select('first_name', 'email')
]);
@ -80,7 +80,7 @@ module.exports = function(knex) {
it('has a "distinct" clause', function() {
return when.all([
return Promise.all([
knex('accounts').select().distinct('email').where('logins', 2).orderBy('email'),
knex('accounts').distinct('email').select().orderBy('email')
]);
@ -89,7 +89,7 @@ module.exports = function(knex) {
it('does "orWhere" cases', function() {
return when.all([
return Promise.all([
knex('accounts').where('id', 1).orWhere('id', '>', 2).select('first_name', 'last_name')
// More tests can be added here.
]);
@ -98,7 +98,7 @@ module.exports = function(knex) {
it('does "andWhere" cases', function() {
return when.all([
return Promise.all([
knex('accounts').select('first_name', 'last_name', 'about').where('id', 1).andWhere('email', 'test@example.com')
]);
@ -106,7 +106,7 @@ module.exports = function(knex) {
it('takes a function to wrap nested where statements', function() {
return when.all([
return Promise.all([
knex('accounts').where(function() {
this.where('id', 2);
this.orWhere('id', 3);
@ -117,7 +117,7 @@ module.exports = function(knex) {
it('handles "where in" cases', function() {
return when.all([
return Promise.all([
knex('accounts').whereIn('id', [1, 2, 3]).select()
]);

View File

@ -1,6 +1,6 @@
var _ = require('underscore');
var Knex = require('../../knex');
var nodefn = require('when/node/function');
var _ = require('underscore');
var Knex = require('../../knex');
var Promise = testPromise;
var config = require(process.env.KNEX_TEST || './config');
@ -19,7 +19,7 @@ var MySQL = Knex.initialize({
connection: config.mysql,
pool: _.extend({}, pool, {
afterCreate: function(connection, callback) {
nodefn.call(connection.query.bind(connection), "SET sql_mode='TRADITIONAL';", []).then(function() {
Promise.promisify(connection.query, connection)("SET sql_mode='TRADITIONAL';", []).then(function() {
callback(null, connection);
});
}

View File

@ -1,5 +1,5 @@
var _ = require('underscore');
var when = require('when');
var Promise = testPromise;
var Builder = require('../../lib/builder').Builder;
var Common = require('../../lib/common').Common;
var Raw = require('../../lib/raw').Raw;
@ -10,7 +10,7 @@ describe('Builder', function () {
beforeEach(function() {
builder = new Builder({
query: function(obj) {
return when.resolve(obj);
return Promise.fulfilled(obj);
},
grammar: require('../../clients/server/mysql/grammar').grammar
});

View File

@ -1,5 +1,4 @@
var when = require('when');
var Builder = require('../../../lib/builder').Builder;
var Common = require('../../../lib/common').Common;
var Raw = require('../../../lib/raw').Raw;

View File

@ -1,12 +1,12 @@
var _ = require('underscore');
var when = require('when');
var Promise = testPromise;
var Pool = require('../../../clients/pool').Pool;
var GenericPool = require('generic-pool-redux').Pool;
describe('Pool', function () {
var connStub = function() {
return when.resolve({end: function() {}});
return Promise.fulfilled({end: function() {}});
};
describe('constructor', function() {

View File

@ -1,5 +1,5 @@
var Transaction = require('../../lib/transaction').Transaction;
var when = require('when');
var Transaction = require('../../lib/transaction').Transaction;
var Promise = testPromise;
var conn = {
conn_obj: true
@ -9,7 +9,7 @@ var knex = {
client: {
name: 'mysql',
startTransaction: function() {
return when(conn);
return Promise.fulfilled(conn);
}
}
};