20
20
21
21
'use strict' ;
22
22
23
- var extend = require ( 'extend' ) ;
24
- var is = require ( 'is' ) ;
25
- var nodeutil = require ( 'util' ) ;
23
+ var events = require ( 'events' ) ;
24
+ var modelo = require ( 'modelo' ) ;
26
25
27
26
/**
28
27
* @type {module:common/serviceObject }
@@ -79,6 +78,30 @@ var util = require('../common/util.js');
79
78
* //-
80
79
* var zone = gce.zone('us-central1-a');
81
80
* var operation = zone.operation('operation-id');
81
+ *
82
+ * //-
83
+ * // All operations are event emitters. The status of each operation is polled
84
+ * // continuously, starting only after you register a "complete" listener.
85
+ * //-
86
+ * operation.on('complete', function(metadata) {
87
+ * // The operation is complete.
88
+ * });
89
+ *
90
+ * //-
91
+ * // Be sure to register an error handler as well to catch any issues which
92
+ * // impeded the operation.
93
+ * //-
94
+ * operation.on('error', function(err) {
95
+ * // An error occurred during the operation.
96
+ * });
97
+ *
98
+ * //-
99
+ * // To force the Operation object to stop polling for updates, simply remove
100
+ * // any "complete" listeners you've registered.
101
+ * //
102
+ * // The easiest way to do this is with `removeAllListeners()`.
103
+ * //-
104
+ * operation.removeAllListeners();
82
105
*/
83
106
function Operation ( scope , name ) {
84
107
var isCompute = scope . constructor . name === 'Compute' ;
@@ -132,10 +155,16 @@ function Operation(scope, name) {
132
155
methods : methods
133
156
} ) ;
134
157
158
+ events . EventEmitter . call ( this ) ;
159
+
160
+ this . completeListeners = 0 ;
161
+ this . hasActiveListeners = false ;
135
162
this . name = name ;
163
+
164
+ this . listenForEvents_ ( ) ;
136
165
}
137
166
138
- nodeutil . inherits ( Operation , ServiceObject ) ;
167
+ modelo . inherits ( Operation , ServiceObject , events . EventEmitter ) ;
139
168
140
169
/**
141
170
* Get the operation's metadata. For a detailed description of metadata see
@@ -167,9 +196,9 @@ Operation.prototype.getMetadata = function(callback) {
167
196
// this callback. We have to make sure this isn't a false error by seeing if
168
197
// the response body contains a property that wouldn't exist on a failed API
169
198
// request (`name`).
170
- var isActualError = err && ( ! apiResponse || apiResponse . name !== self . name ) ;
199
+ var requestFailed = err && ( ! apiResponse || apiResponse . name !== self . name ) ;
171
200
172
- if ( isActualError ) {
201
+ if ( requestFailed ) {
173
202
callback ( err , null , apiResponse ) ;
174
203
return ;
175
204
}
@@ -181,80 +210,70 @@ Operation.prototype.getMetadata = function(callback) {
181
210
} ;
182
211
183
212
/**
184
- * Register a callback for when the operation is complete.
213
+ * Begin listening for events on the operation. This method keeps track of how
214
+ * many "complete" listeners are registered and removed, making sure polling is
215
+ * handled automatically.
185
216
*
186
- * If the operation doesn't complete after the maximum number of attempts have
187
- * been made (see `options.maxAttempts` and `options.interval`), an error will
188
- * be provided to your callback with code: `OPERATION_INCOMPLETE`.
217
+ * As long as there is one active "complete" listener, the connection is open.
218
+ * When there are no more listeners, the polling stops.
189
219
*
190
- * @param {object= } options - Configuration object.
191
- * @param {number } options.maxAttempts - Maximum number of attempts to make an
192
- * API request to check if the operation is complete. (Default: `10`)
193
- * @param {number } options.interval - Amount of time in milliseconds between
194
- * each request. (Default: `3000`)
195
- * @param {function } callback - The callback function.
196
- * @param {?error } callback.err - An error returned while making this request.
197
- * @param {object } callback.metadata - The operation's metadata.
198
- *
199
- * @example
200
- * operation.onComplete(function(err, metadata) {
201
- * if (err.code === 'OPERATION_INCOMPLETE') {
202
- * // The operation is not complete yet. You may want to register another
203
- * // `onComplete` listener or queue for later.
204
- * }
205
- *
206
- * if (!err) {
207
- * // Operation complete!
208
- * }
209
- * });
220
+ * @private
210
221
*/
211
- Operation . prototype . onComplete = function ( options , callback ) {
222
+ Operation . prototype . listenForEvents_ = function ( ) {
212
223
var self = this ;
213
224
214
- if ( is . fn ( options ) ) {
215
- callback = options ;
216
- options = { } ;
217
- }
218
-
219
- options = extend ( {
220
- maxAttempts : 10 ,
221
- interval : 3000
222
- } , options ) ;
223
-
224
- var didNotCompleteError = new Error ( 'Operation did not complete.' ) ;
225
- didNotCompleteError . code = 'OPERATION_INCOMPLETE' ;
226
-
227
- var numAttempts = 0 ;
225
+ this . on ( 'newListener' , function ( event ) {
226
+ if ( event === 'complete' ) {
227
+ self . completeListeners ++ ;
228
228
229
- function checkMetadata ( ) {
230
- numAttempts ++ ;
229
+ if ( ! self . hasActiveListeners ) {
230
+ self . hasActiveListeners = true ;
231
+ self . startPolling_ ( ) ;
232
+ }
233
+ }
234
+ } ) ;
231
235
232
- if ( numAttempts > options . maxAttempts ) {
233
- callback ( didNotCompleteError , self . metadata ) ;
234
- return ;
236
+ this . on ( 'removeListener' , function ( event ) {
237
+ if ( event === 'complete' && -- self . completeListeners === 0 ) {
238
+ self . hasActiveListeners = false ;
235
239
}
240
+ } ) ;
241
+ } ;
236
242
237
- setTimeout ( function ( ) {
238
- self . getMetadata ( onMetadata ) ;
239
- } , options . interval ) ;
243
+ /**
244
+ * Poll `getMetadata` to check the operation's status. This runs a loop to ping
245
+ * the API on an interval.
246
+ *
247
+ * Note: This method is automatically called once a "complete" event handler is
248
+ * registered on the operation.
249
+ *
250
+ * @private
251
+ */
252
+ Operation . prototype . startPolling_ = function ( ) {
253
+ var self = this ;
254
+
255
+ if ( ! this . hasActiveListeners ) {
256
+ return ;
240
257
}
241
258
242
- function onMetadata ( err , metadata ) {
259
+ this . getMetadata ( function ( err , metadata , apiResponse ) {
260
+ // Parsing the response body will automatically create an ApiError object if
261
+ // the operation failed.
262
+ var parsedHttpRespBody = util . parseHttpRespBody ( apiResponse ) ;
263
+ err = err || parsedHttpRespBody . err ;
264
+
243
265
if ( err ) {
244
- callback ( err , metadata ) ;
266
+ self . emit ( 'error' , err ) ;
245
267
return ;
246
268
}
247
269
248
270
if ( metadata . status !== 'DONE' ) {
249
- checkMetadata ( ) ;
271
+ setTimeout ( self . startPolling_ . bind ( self ) , 500 ) ;
250
272
return ;
251
273
}
252
274
253
- // The operation is complete.
254
- callback ( null , metadata ) ;
255
- }
256
-
257
- checkMetadata ( ) ;
275
+ self . emit ( 'complete' , metadata ) ;
276
+ } ) ;
258
277
} ;
259
278
260
279
module . exports = Operation ;
0 commit comments