Skip to content

Commit

Permalink
core: make operations promise friendly (#1689)
Browse files Browse the repository at this point in the history
  • Loading branch information
callmehiphop authored and stephenplusplus committed Oct 14, 2016
1 parent 7622a4e commit 340f3d8
Show file tree
Hide file tree
Showing 12 changed files with 224 additions and 42 deletions.
25 changes: 25 additions & 0 deletions packages/bigquery/src/job.js
Original file line number Diff line number Diff line change
Expand Up @@ -312,6 +312,31 @@ Job.prototype.getQueryResultsStream = function(options) {
return this.bigQuery.createQueryStream(options);
};

/**
* Convenience method that wraps the `complete` and `error` events in a
* Promise.
*
* @return {promise}
*
* @example
* job.promise().then(function(metadata) {
* // The job is complete.
* }, function(err) {
* // An error occurred during the job.
* });
*/
Job.prototype.promise = function() {
var self = this;

return new self.Promise(function(resolve, reject) {
self
.on('error', reject)
.on('complete', function(metadata) {
resolve([metadata]);
});
});
};

/**
* Begin listening for events on the job. This method keeps track of how many
* "complete" listeners are registered and removed, making sure polling is
Expand Down
42 changes: 41 additions & 1 deletion packages/bigquery/test/job.js
Original file line number Diff line number Diff line change
Expand Up @@ -50,7 +50,8 @@ var fakeUtil = Object.keys(util).reduce(function(fakeUtil, methodName) {

describe('BigQuery/Job', function() {
var BIGQUERY = {
projectId: 'my-project'
projectId: 'my-project',
Promise: Promise
};
var JOB_ID = 'job_XYrk_3z';
var Job;
Expand Down Expand Up @@ -274,6 +275,45 @@ describe('BigQuery/Job', function() {
});
});

describe('promise', function() {
beforeEach(function() {
job.startPolling_ = util.noop;
});

it('should return an instance of the localized Promise', function() {
var FakePromise = job.Promise = function() {};
var promise = job.promise();

assert(promise instanceof FakePromise);
});

it('should reject the promise if an error occurs', function() {
var error = new Error('err');

setImmediate(function() {
job.emit('error', error);
});

return job.promise().then(function() {
throw new Error('Promise should have been rejected.');
}, function(err) {
assert.strictEqual(err, error);
});
});

it('should resolve the promise on complete', function() {
var metadata = {};

setImmediate(function() {
job.emit('complete', metadata);
});

return job.promise().then(function(data) {
assert.deepEqual(data, [metadata]);
});
});
});

describe('listenForEvents_', function() {
beforeEach(function() {
job.startPolling_ = util.noop;
Expand Down
17 changes: 17 additions & 0 deletions packages/common/src/grpc-operation.js
Original file line number Diff line number Diff line change
Expand Up @@ -133,6 +133,23 @@ GrpcOperation.prototype.cancel = function(callback) {
this.request(protoOpts, reqOpts, callback || util.noop);
};

/**
* Wraps the `complete` and `error` events in a Promise.
*
* @return {promise}
*/
GrpcOperation.prototype.promise = function() {
var self = this;

return new self.Promise(function(resolve, reject) {
self
.on('error', reject)
.on('complete', function(metadata) {
resolve([metadata]);
});
});
};

/**
* Begin listening for events on the operation. This method keeps track of how
* many "complete" listeners are registered and removed, making sure polling is
Expand Down
1 change: 1 addition & 0 deletions packages/common/src/service-object.js
Original file line number Diff line number Diff line change
Expand Up @@ -71,6 +71,7 @@ function ServiceObject(config) {
this.createMethod = config.createMethod;
this.methods = config.methods || {};
this.interceptors = [];
this.Promise = this.parent.Promise;

if (config.methods) {
var allMethodNames = Object.keys(ServiceObject.prototype);
Expand Down
5 changes: 1 addition & 4 deletions packages/common/src/service.js
Original file line number Diff line number Diff line change
Expand Up @@ -60,10 +60,7 @@ function Service(config, options) {
this.packageJson = config.packageJson;
this.projectId = options.projectId;
this.projectIdRequired = config.projectIdRequired !== false;

if (options.promise) {
util.setPromiseOverride(options.promise);
}
this.Promise = options.promise || Promise;
}

/**
Expand Down
23 changes: 8 additions & 15 deletions packages/common/src/util.js
Original file line number Diff line number Diff line change
Expand Up @@ -51,8 +51,6 @@ var errorMessage = format([
path: '/docs/guides/authentication'
});

var PromiseOverride;

var missingProjectIdError = new Error(errorMessage);

util.missingProjectIdError = missingProjectIdError;
Expand Down Expand Up @@ -656,7 +654,14 @@ function promisify(originalMethod) {
return originalMethod.apply(context, args);
}

var PromiseCtor = PromiseOverride || Promise;
var PromiseCtor = Promise;

// Because dedupe will likely create a single install of
// @google-cloud/common to be shared amongst all modules, we need to
// localize it at the Service level.
if (context && context.Promise) {
PromiseCtor = context.Promise;
}

return new PromiseCtor(function(resolve, reject) {
args.push(function() {
Expand Down Expand Up @@ -708,15 +713,3 @@ function promisifyAll(Class, options) {
}

util.promisifyAll = promisifyAll;

/**
* Allows user to override the Promise constructor without the need to touch
* globals. Override should be ES6 Promise compliant.
*
* @param {promise} override
*/
function setPromiseOverride(override) {
PromiseOverride = override;
}

module.exports.setPromiseOverride = setPromiseOverride;
43 changes: 42 additions & 1 deletion packages/common/test/grpc-operation.js
Original file line number Diff line number Diff line change
Expand Up @@ -47,7 +47,9 @@ var FakeGrpcServiceObject = createFake(GrpcServiceObject);
var FakeGrpcService = createFake(GrpcService);

describe('GrpcOperation', function() {
var FAKE_SERVICE = {};
var FAKE_SERVICE = {
Promise: Promise
};
var OPERATION_ID = '/a/b/c/d';

var GrpcOperation;
Expand Down Expand Up @@ -149,6 +151,45 @@ describe('GrpcOperation', function() {
});
});

describe('promise', function() {
beforeEach(function() {
grpcOperation.startPolling_ = util.noop;
});

it('should return an instance of the localized Promise', function() {
var FakePromise = grpcOperation.Promise = function() {};
var promise = grpcOperation.promise();

assert(promise instanceof FakePromise);
});

it('should reject the promise if an error occurs', function() {
var error = new Error('err');

setImmediate(function() {
grpcOperation.emit('error', error);
});

return grpcOperation.promise().then(function() {
throw new Error('Promise should have been rejected.');
}, function(err) {
assert.strictEqual(err, error);
});
});

it('should resolve the promise on complete', function() {
var metadata = {};

setImmediate(function() {
grpcOperation.emit('complete', metadata);
});

return grpcOperation.promise().then(function(data) {
assert.deepEqual(data, [metadata]);
});
});
});

describe('listenForEvents_', function() {
beforeEach(function() {
grpcOperation.startPolling_ = util.noop;
Expand Down
13 changes: 13 additions & 0 deletions packages/common/test/service-object.js
Original file line number Diff line number Diff line change
Expand Up @@ -109,6 +109,19 @@ describe('ServiceObject', function() {
assert.strictEqual(typeof serviceObject.create, 'function');
assert.strictEqual(serviceObject.delete, undefined);
});

it('should localize the Promise object', function() {
var FakePromise = function() {};
var config = extend({}, CONFIG, {
parent: {
Promise: FakePromise
}
});

var serviceObject = new ServiceObject(config);

assert.strictEqual(serviceObject.Promise, FakePromise);
});
});

describe('create', function() {
Expand Down
12 changes: 6 additions & 6 deletions packages/common/test/service.js
Original file line number Diff line number Diff line change
Expand Up @@ -150,15 +150,15 @@ describe('Service', function() {
assert.strictEqual(service.projectIdRequired, true);
});

it('should call setPromiseOverride when promise is set', function(done) {
it('should localize the Promise object', function() {
var FakePromise = function() {};
var service = new Service({}, { promise: FakePromise });

util.setPromiseOverride = function(override) {
assert.strictEqual(override, FakePromise);
done();
};
assert.strictEqual(service.Promise, FakePromise);
});

new Service({}, { promise: FakePromise });
it('should localize the native Promise object by default', function() {
assert.strictEqual(service.Promise, global.Promise);
});
});

Expand Down
17 changes: 3 additions & 14 deletions packages/common/test/util.js
Original file line number Diff line number Diff line change
Expand Up @@ -1494,21 +1494,10 @@ describe('common/util', function() {
assert.strictEqual(err, error);
});
});
});

describe('setPromiseOverride', function() {
var FakePromise = function() {};

before(function() {
util.setPromiseOverride(FakePromise);
});

after(function() {
util.setPromiseOverride(null);
});

it('should allow the Promise constructor to be specified', function() {
var promise = util.promisify(util.noop)();
it('should allow the Promise object to be overridden', function() {
var FakePromise = function() {};
var promise = func.call({ Promise: FakePromise });

assert(promise instanceof FakePromise);
});
Expand Down
25 changes: 25 additions & 0 deletions packages/compute/src/operation.js
Original file line number Diff line number Diff line change
Expand Up @@ -230,6 +230,31 @@ Operation.prototype.getMetadata = function(callback) {
});
};

/**
* Convenience method that wraps the `complete` and `error` events in a
* Promise.
*
* @return {promise}
*
* @example
* operation.promise().then(function(metadata) {
* // The operation is complete.
* }, function(err) {
* // An error occurred during the operation.
* });
*/
Operation.prototype.promise = function() {
var self = this;

return new self.Promise(function(resolve, reject) {
self
.on('error', reject)
.on('complete', function(metadata) {
resolve([metadata]);
});
});
};

/**
* Begin listening for events on the operation. This method keeps track of how
* many "complete" listeners are registered and removed, making sure polling is
Expand Down
43 changes: 42 additions & 1 deletion packages/compute/test/operation.js
Original file line number Diff line number Diff line change
Expand Up @@ -51,7 +51,9 @@ describe('Operation', function() {
var Operation;
var operation;

var SCOPE = {};
var SCOPE = {
Promise: Promise
};
var OPERATION_NAME = 'operation-name';

before(function() {
Expand Down Expand Up @@ -203,6 +205,45 @@ describe('Operation', function() {
});
});

describe('promise', function() {
beforeEach(function() {
operation.startPolling_ = util.noop;
});

it('should return an instance of the localized Promise', function() {
var FakePromise = operation.Promise = function() {};
var promise = operation.promise();

assert(promise instanceof FakePromise);
});

it('should reject the promise if an error occurs', function() {
var error = new Error('err');

setImmediate(function() {
operation.emit('error', error);
});

return operation.promise().then(function() {
throw new Error('Promise should have been rejected.');
}, function(err) {
assert.strictEqual(err, error);
});
});

it('should resolve the promise on complete', function() {
var metadata = {};

setImmediate(function() {
operation.emit('complete', metadata);
});

return operation.promise().then(function(data) {
assert.deepEqual(data, [metadata]);
});
});
});

describe('listenForEvents_', function() {
beforeEach(function() {
operation.startPolling_ = util.noop;
Expand Down

0 comments on commit 340f3d8

Please sign in to comment.