Skip to content

Commit dd76100

Browse files
common: add Operation object
1 parent 881ef7c commit dd76100

File tree

8 files changed

+604
-419
lines changed

8 files changed

+604
-419
lines changed

packages/common/src/grpc-operation.js

+6-91
Original file line numberDiff line numberDiff line change
@@ -20,20 +20,19 @@
2020

2121
'use strict';
2222

23-
var events = require('events');
2423
var modelo = require('modelo');
2524

2625
/**
27-
* @type {module:common/grpcService}
26+
* @type {module:common/grpcServiceObject}
2827
* @private
2928
*/
30-
var GrpcService = require('./grpc-service.js');
29+
var GrpcServiceObject = require('./grpc-service-object.js');
3130

3231
/**
33-
* @type {module:common/grpcServiceObject}
32+
* @type {module:common/operation}
3433
* @private
3534
*/
36-
var GrpcServiceObject = require('./grpc-service-object.js');
35+
var Operation = require('./operation.js');
3736

3837
/**
3938
* @type {module:common/util}
@@ -101,16 +100,11 @@ function GrpcOperation(parent, name) {
101100
methods: methods
102101
};
103102

103+
Operation.call(this, config);
104104
GrpcServiceObject.call(this, config);
105-
events.EventEmitter.call(this);
106-
107-
this.completeListeners = 0;
108-
this.hasActiveListeners = false;
109-
110-
this.listenForEvents_();
111105
}
112106

113-
modelo.inherits(GrpcOperation, GrpcServiceObject, events.EventEmitter);
107+
modelo.inherits(GrpcOperation, GrpcServiceObject, Operation);
114108

115109
/**
116110
* Cancel the operation.
@@ -133,83 +127,4 @@ GrpcOperation.prototype.cancel = function(callback) {
133127
this.request(protoOpts, reqOpts, callback || util.noop);
134128
};
135129

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-
153-
/**
154-
* Begin listening for events on the operation. This method keeps track of how
155-
* many "complete" listeners are registered and removed, making sure polling is
156-
* handled automatically.
157-
*
158-
* As long as there is one active "complete" listener, the connection is open.
159-
* When there are no more listeners, the polling stops.
160-
*
161-
* @private
162-
*/
163-
GrpcOperation.prototype.listenForEvents_ = function() {
164-
var self = this;
165-
166-
this.on('newListener', function(event) {
167-
if (event === 'complete') {
168-
self.completeListeners++;
169-
170-
if (!self.hasActiveListeners) {
171-
self.hasActiveListeners = true;
172-
self.startPolling_();
173-
}
174-
}
175-
});
176-
177-
this.on('removeListener', function(event) {
178-
if (event === 'complete' && --self.completeListeners === 0) {
179-
self.hasActiveListeners = false;
180-
}
181-
});
182-
};
183-
184-
/**
185-
* Poll `getMetadata` to check the operation's status. This runs a loop to ping
186-
* the API on an interval.
187-
*
188-
* Note: This method is automatically called once a "complete" event handler is
189-
* registered on the operation.
190-
*
191-
* @private
192-
*/
193-
GrpcOperation.prototype.startPolling_ = function() {
194-
var self = this;
195-
196-
if (!this.hasActiveListeners) {
197-
return;
198-
}
199-
200-
this.getMetadata(function(err, resp) {
201-
if (err || resp.error) {
202-
self.emit('error', err || GrpcService.decorateStatus_(resp.result));
203-
return;
204-
}
205-
206-
if (!resp.done) {
207-
setTimeout(self.startPolling_.bind(self), 500);
208-
return;
209-
}
210-
211-
self.emit('complete', resp);
212-
});
213-
};
214-
215130
module.exports = GrpcOperation;

packages/common/src/index.js

+6
Original file line numberDiff line numberDiff line change
@@ -32,6 +32,12 @@ exports.GrpcService = require('./grpc-service.js');
3232
*/
3333
exports.GrpcServiceObject = require('./grpc-service-object.js');
3434

35+
/**
36+
* @type {module:common/operation}
37+
* @private
38+
*/
39+
exports.Operation = require('./operation.js');
40+
3541
/**
3642
* @type {module:common/paginator}
3743
* @private

packages/common/src/operation.js

+198
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,198 @@
1+
/*!
2+
* Copyright 2016 Google Inc. All Rights Reserved.
3+
*
4+
* Licensed under the Apache License, Version 2.0 (the "License");
5+
* you may not use this file except in compliance with the License.
6+
* You may obtain a copy of the License at
7+
*
8+
* http://www.apache.org/licenses/LICENSE-2.0
9+
*
10+
* Unless required by applicable law or agreed to in writing, software
11+
* distributed under the License is distributed on an "AS IS" BASIS,
12+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13+
* See the License for the specific language governing permissions and
14+
* limitations under the License.
15+
*/
16+
17+
/*!
18+
* @module common/operation
19+
*/
20+
21+
'use strict';
22+
23+
var events = require('events');
24+
var extend = require('extend');
25+
var modelo = require('modelo');
26+
27+
/**
28+
* @type {module:common/service}
29+
* @private
30+
*/
31+
var GrpcService = require('./grpc-service.js');
32+
33+
/**
34+
* @type {module:common/serviceObject}
35+
* @private
36+
*/
37+
var ServiceObject = require('./service-object.js');
38+
39+
// jscs:disable maximumLineLength
40+
/**
41+
* An Operation object allows you to interact with APIs that take longer to
42+
* process things.
43+
*
44+
* @constructor
45+
* @alias module:common/operation
46+
*
47+
* @param {object} config - Configuration object.
48+
* @param {module:common/service|module:common/serviceObject|module:common/grpcService|module:common/grpcServiceObject} config.parent - The
49+
* parent object.
50+
* @param {string} id - The operation ID.
51+
*/
52+
// jscs:enable maximumLineLength
53+
function Operation(config) {
54+
var methods = {
55+
/**
56+
* Checks to see if an operation exists.
57+
*/
58+
exists: true,
59+
60+
/**
61+
* Retrieves the operation.
62+
*/
63+
get: true,
64+
65+
/**
66+
* Retrieves metadata for the operation.
67+
*/
68+
getMetadata: {
69+
reqOpts: {
70+
name: config.id
71+
}
72+
}
73+
};
74+
75+
config = extend({
76+
baseUrl: ''
77+
}, config);
78+
79+
config.methods = config.methods || methods;
80+
81+
ServiceObject.call(this, config);
82+
events.EventEmitter.call(this);
83+
84+
this.completeListeners = 0;
85+
this.hasActiveListeners = false;
86+
87+
this.listenForEvents_();
88+
}
89+
90+
modelo.inherits(Operation, ServiceObject, events.EventEmitter);
91+
92+
/**
93+
* Wraps the `complete` and `error` events in a Promise.
94+
*
95+
* @return {promise}
96+
*/
97+
Operation.prototype.promise = function() {
98+
var self = this;
99+
100+
return new self.Promise(function(resolve, reject) {
101+
self
102+
.on('error', reject)
103+
.on('complete', function(metadata) {
104+
resolve([metadata]);
105+
});
106+
});
107+
};
108+
109+
/**
110+
* Begin listening for events on the operation. This method keeps track of how
111+
* many "complete" listeners are registered and removed, making sure polling is
112+
* handled automatically.
113+
*
114+
* As long as there is one active "complete" listener, the connection is open.
115+
* When there are no more listeners, the polling stops.
116+
*
117+
* @private
118+
*/
119+
Operation.prototype.listenForEvents_ = function() {
120+
var self = this;
121+
122+
this.on('newListener', function(event) {
123+
if (event === 'complete') {
124+
self.completeListeners++;
125+
126+
if (!self.hasActiveListeners) {
127+
self.hasActiveListeners = true;
128+
self.startPolling_();
129+
}
130+
}
131+
});
132+
133+
this.on('removeListener', function(event) {
134+
if (event === 'complete' && --self.completeListeners === 0) {
135+
self.hasActiveListeners = false;
136+
}
137+
});
138+
};
139+
140+
/**
141+
* Poll for a status update. Execute the callback:
142+
*
143+
* - callback(err): Operation failed
144+
* - callback(): Operation incomplete
145+
* - callback(null, metadata): Operation complete
146+
*
147+
* @private
148+
*
149+
* @param {function} callback
150+
*/
151+
Operation.prototype.poll_ = function(callback) {
152+
this.getMetadata(function(err, resp) {
153+
if (err || resp.error) {
154+
callback(err || GrpcService.decorateGrpcStatus_(resp.error));
155+
return;
156+
}
157+
158+
if (!resp.done) {
159+
callback();
160+
return;
161+
}
162+
163+
callback(null, resp);
164+
});
165+
};
166+
167+
/**
168+
* Poll `getMetadata` to check the operation's status. This runs a loop to ping
169+
* the API on an interval.
170+
*
171+
* Note: This method is automatically called once a "complete" event handler is
172+
* registered on the operation.
173+
*
174+
* @private
175+
*/
176+
Operation.prototype.startPolling_ = function() {
177+
var self = this;
178+
179+
if (!this.hasActiveListeners) {
180+
return;
181+
}
182+
183+
this.poll_(function(err, metadata) {
184+
if (err) {
185+
self.emit('error', err);
186+
return;
187+
}
188+
189+
if (!metadata) {
190+
setTimeout(self.startPolling_.bind(self), 500);
191+
return;
192+
}
193+
194+
self.emit('complete', metadata);
195+
});
196+
};
197+
198+
module.exports = Operation;

packages/common/src/util.js

+6-12
Original file line numberDiff line numberDiff line change
@@ -72,7 +72,7 @@ util.noop = noop;
7272
*
7373
* @param {object} errorBody - Error object.
7474
*/
75-
util.ApiError = createErrorClass('ApiError', function(errorBody) {
75+
var ApiError = createErrorClass('ApiError', function(errorBody) {
7676
this.code = errorBody.code;
7777
this.errors = errorBody.errors;
7878
this.response = errorBody.response;
@@ -99,19 +99,13 @@ util.ApiError = createErrorClass('ApiError', function(errorBody) {
9999
});
100100

101101
/**
102-
* Custom error type for partial errors returned from the API.
102+
* Wrap the ApiError constructor so context isn't lost.
103103
*
104-
* @param {object} b - Error object.
104+
* @param {object} errorBody - Error object.
105105
*/
106-
util.PartialFailureError = createErrorClass('PartialFailureError', function(b) {
107-
var errorObject = b;
108-
109-
this.errors = errorObject.errors;
110-
this.response = errorObject.response;
111-
112-
var defaultErrorMessage = 'A failure occurred during this request.';
113-
this.message = errorObject.message || defaultErrorMessage;
114-
});
106+
util.ApiError = function(errorBody) {
107+
return new ApiError(errorBody);
108+
};
115109

116110
/**
117111
* Uniformly process an API response.

0 commit comments

Comments
 (0)