Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

core: make operations promise friendly #1689

Merged
merged 5 commits into from
Oct 14, 2016
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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