Skip to content

Commit 6899963

Browse files
committed
core: make operations promise friendly (#1689)
1 parent a45cb3f commit 6899963

12 files changed

Lines changed: 224 additions & 42 deletions

File tree

packages/bigquery/src/job.js

Lines changed: 25 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -312,6 +312,31 @@ Job.prototype.getQueryResultsStream = function(options) {
312312
return this.bigQuery.createQueryStream(options);
313313
};
314314

315+
/**
316+
* Convenience method that wraps the `complete` and `error` events in a
317+
* Promise.
318+
*
319+
* @return {promise}
320+
*
321+
* @example
322+
* job.promise().then(function(metadata) {
323+
* // The job is complete.
324+
* }, function(err) {
325+
* // An error occurred during the job.
326+
* });
327+
*/
328+
Job.prototype.promise = function() {
329+
var self = this;
330+
331+
return new self.Promise(function(resolve, reject) {
332+
self
333+
.on('error', reject)
334+
.on('complete', function(metadata) {
335+
resolve([metadata]);
336+
});
337+
});
338+
};
339+
315340
/**
316341
* Begin listening for events on the job. This method keeps track of how many
317342
* "complete" listeners are registered and removed, making sure polling is

packages/bigquery/test/job.js

Lines changed: 41 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -50,7 +50,8 @@ var fakeUtil = Object.keys(util).reduce(function(fakeUtil, methodName) {
5050

5151
describe('BigQuery/Job', function() {
5252
var BIGQUERY = {
53-
projectId: 'my-project'
53+
projectId: 'my-project',
54+
Promise: Promise
5455
};
5556
var JOB_ID = 'job_XYrk_3z';
5657
var Job;
@@ -274,6 +275,45 @@ describe('BigQuery/Job', function() {
274275
});
275276
});
276277

278+
describe('promise', function() {
279+
beforeEach(function() {
280+
job.startPolling_ = util.noop;
281+
});
282+
283+
it('should return an instance of the localized Promise', function() {
284+
var FakePromise = job.Promise = function() {};
285+
var promise = job.promise();
286+
287+
assert(promise instanceof FakePromise);
288+
});
289+
290+
it('should reject the promise if an error occurs', function() {
291+
var error = new Error('err');
292+
293+
setImmediate(function() {
294+
job.emit('error', error);
295+
});
296+
297+
return job.promise().then(function() {
298+
throw new Error('Promise should have been rejected.');
299+
}, function(err) {
300+
assert.strictEqual(err, error);
301+
});
302+
});
303+
304+
it('should resolve the promise on complete', function() {
305+
var metadata = {};
306+
307+
setImmediate(function() {
308+
job.emit('complete', metadata);
309+
});
310+
311+
return job.promise().then(function(data) {
312+
assert.deepEqual(data, [metadata]);
313+
});
314+
});
315+
});
316+
277317
describe('listenForEvents_', function() {
278318
beforeEach(function() {
279319
job.startPolling_ = util.noop;

packages/common/src/grpc-operation.js

Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -133,6 +133,23 @@ GrpcOperation.prototype.cancel = function(callback) {
133133
this.request(protoOpts, reqOpts, callback || util.noop);
134134
};
135135

136+
/**
137+
* Wraps the `complete` and `error` events in a Promise.
138+
*
139+
* @return {promise}
140+
*/
141+
GrpcOperation.prototype.promise = function() {
142+
var self = this;
143+
144+
return new self.Promise(function(resolve, reject) {
145+
self
146+
.on('error', reject)
147+
.on('complete', function(metadata) {
148+
resolve([metadata]);
149+
});
150+
});
151+
};
152+
136153
/**
137154
* Begin listening for events on the operation. This method keeps track of how
138155
* many "complete" listeners are registered and removed, making sure polling is

packages/common/src/service-object.js

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -71,6 +71,7 @@ function ServiceObject(config) {
7171
this.createMethod = config.createMethod;
7272
this.methods = config.methods || {};
7373
this.interceptors = [];
74+
this.Promise = this.parent.Promise;
7475

7576
if (config.methods) {
7677
var allMethodNames = Object.keys(ServiceObject.prototype);

packages/common/src/service.js

Lines changed: 1 addition & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -60,10 +60,7 @@ function Service(config, options) {
6060
this.packageJson = config.packageJson;
6161
this.projectId = options.projectId;
6262
this.projectIdRequired = config.projectIdRequired !== false;
63-
64-
if (options.promise) {
65-
util.setPromiseOverride(options.promise);
66-
}
63+
this.Promise = options.promise || Promise;
6764
}
6865

6966
/**

packages/common/src/util.js

Lines changed: 8 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -51,8 +51,6 @@ var errorMessage = format([
5151
path: '/docs/guides/authentication'
5252
});
5353

54-
var PromiseOverride;
55-
5654
var missingProjectIdError = new Error(errorMessage);
5755

5856
util.missingProjectIdError = missingProjectIdError;
@@ -656,7 +654,14 @@ function promisify(originalMethod) {
656654
return originalMethod.apply(context, args);
657655
}
658656

659-
var PromiseCtor = PromiseOverride || Promise;
657+
var PromiseCtor = Promise;
658+
659+
// Because dedupe will likely create a single install of
660+
// @google-cloud/common to be shared amongst all modules, we need to
661+
// localize it at the Service level.
662+
if (context && context.Promise) {
663+
PromiseCtor = context.Promise;
664+
}
660665

661666
return new PromiseCtor(function(resolve, reject) {
662667
args.push(function() {
@@ -708,15 +713,3 @@ function promisifyAll(Class, options) {
708713
}
709714

710715
util.promisifyAll = promisifyAll;
711-
712-
/**
713-
* Allows user to override the Promise constructor without the need to touch
714-
* globals. Override should be ES6 Promise compliant.
715-
*
716-
* @param {promise} override
717-
*/
718-
function setPromiseOverride(override) {
719-
PromiseOverride = override;
720-
}
721-
722-
module.exports.setPromiseOverride = setPromiseOverride;

packages/common/test/grpc-operation.js

Lines changed: 42 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -47,7 +47,9 @@ var FakeGrpcServiceObject = createFake(GrpcServiceObject);
4747
var FakeGrpcService = createFake(GrpcService);
4848

4949
describe('GrpcOperation', function() {
50-
var FAKE_SERVICE = {};
50+
var FAKE_SERVICE = {
51+
Promise: Promise
52+
};
5153
var OPERATION_ID = '/a/b/c/d';
5254

5355
var GrpcOperation;
@@ -149,6 +151,45 @@ describe('GrpcOperation', function() {
149151
});
150152
});
151153

154+
describe('promise', function() {
155+
beforeEach(function() {
156+
grpcOperation.startPolling_ = util.noop;
157+
});
158+
159+
it('should return an instance of the localized Promise', function() {
160+
var FakePromise = grpcOperation.Promise = function() {};
161+
var promise = grpcOperation.promise();
162+
163+
assert(promise instanceof FakePromise);
164+
});
165+
166+
it('should reject the promise if an error occurs', function() {
167+
var error = new Error('err');
168+
169+
setImmediate(function() {
170+
grpcOperation.emit('error', error);
171+
});
172+
173+
return grpcOperation.promise().then(function() {
174+
throw new Error('Promise should have been rejected.');
175+
}, function(err) {
176+
assert.strictEqual(err, error);
177+
});
178+
});
179+
180+
it('should resolve the promise on complete', function() {
181+
var metadata = {};
182+
183+
setImmediate(function() {
184+
grpcOperation.emit('complete', metadata);
185+
});
186+
187+
return grpcOperation.promise().then(function(data) {
188+
assert.deepEqual(data, [metadata]);
189+
});
190+
});
191+
});
192+
152193
describe('listenForEvents_', function() {
153194
beforeEach(function() {
154195
grpcOperation.startPolling_ = util.noop;

packages/common/test/service-object.js

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -109,6 +109,19 @@ describe('ServiceObject', function() {
109109
assert.strictEqual(typeof serviceObject.create, 'function');
110110
assert.strictEqual(serviceObject.delete, undefined);
111111
});
112+
113+
it('should localize the Promise object', function() {
114+
var FakePromise = function() {};
115+
var config = extend({}, CONFIG, {
116+
parent: {
117+
Promise: FakePromise
118+
}
119+
});
120+
121+
var serviceObject = new ServiceObject(config);
122+
123+
assert.strictEqual(serviceObject.Promise, FakePromise);
124+
});
112125
});
113126

114127
describe('create', function() {

packages/common/test/service.js

Lines changed: 6 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -150,15 +150,15 @@ describe('Service', function() {
150150
assert.strictEqual(service.projectIdRequired, true);
151151
});
152152

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

156-
util.setPromiseOverride = function(override) {
157-
assert.strictEqual(override, FakePromise);
158-
done();
159-
};
157+
assert.strictEqual(service.Promise, FakePromise);
158+
});
160159

161-
new Service({}, { promise: FakePromise });
160+
it('should localize the native Promise object by default', function() {
161+
assert.strictEqual(service.Promise, global.Promise);
162162
});
163163
});
164164

packages/common/test/util.js

Lines changed: 3 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -1494,21 +1494,10 @@ describe('common/util', function() {
14941494
assert.strictEqual(err, error);
14951495
});
14961496
});
1497-
});
1498-
1499-
describe('setPromiseOverride', function() {
1500-
var FakePromise = function() {};
1501-
1502-
before(function() {
1503-
util.setPromiseOverride(FakePromise);
1504-
});
1505-
1506-
after(function() {
1507-
util.setPromiseOverride(null);
1508-
});
15091497

1510-
it('should allow the Promise constructor to be specified', function() {
1511-
var promise = util.promisify(util.noop)();
1498+
it('should allow the Promise object to be overridden', function() {
1499+
var FakePromise = function() {};
1500+
var promise = func.call({ Promise: FakePromise });
15121501

15131502
assert(promise instanceof FakePromise);
15141503
});

0 commit comments

Comments
 (0)