UNPKG

owltech

Version:
336 lines 13.6 kB
"use strict"; /* * Copyright 2016, Google Inc. * All rights reserved. * * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions are * met: * * * Redistributions of source code must retain the above copyright * notice, this list of conditions and the following disclaimer. * * Redistributions in binary form must reproduce the above * copyright notice, this list of conditions and the following disclaimer * in the documentation and/or other materials provided with the * distribution. * * Neither the name of Google Inc. nor the names of its * contributors may be used to endorse or promote products derived from * this software without specific prior written permission. * * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. */ Object.defineProperty(exports, "__esModule", { value: true }); const events_1 = require("events"); const grpc_1 = require("grpc"); const apiCallable_1 = require("./apiCallable"); const gax_1 = require("./gax"); const GoogleError_1 = require("./GoogleError"); class LongrunningDescriptor { /** * Describes the structure of a page-streaming call. * * @property {OperationsClient} operationsClient * @property {anyDecoder} responseDecoder * @property {anyDecoder} metadataDecoder * * @param {OperationsClient} operationsClient - The client used to poll or * cancel an operation. * @param {anyDecoder=} responseDecoder - The decoder to unpack * the response message. * @param {anyDecoder=} metadataDecoder - The decoder to unpack * the metadata message. * * @constructor */ constructor(operationsClient, responseDecoder, metadataDecoder) { this.operationsClient = operationsClient; this.responseDecoder = responseDecoder; this.metadataDecoder = metadataDecoder; } apiCaller() { return new LongrunningApiCaller(this); } } exports.LongrunningDescriptor = LongrunningDescriptor; class LongrunningApiCaller extends apiCallable_1.NormalApiCaller { /** * Creates an API caller that performs polling on a long running operation. * * @private * @constructor * @param {LongrunningDescriptor} longrunningDescriptor - Holds the * decoders used for unpacking responses and the operationsClient * used for polling the operation. */ constructor(longrunningDescriptor) { super(); this.longrunningDescriptor = longrunningDescriptor; } call(apiCall, argument, settings, canceller) { canceller.call((argument, callback) => { return this._wrapOperation(apiCall, settings, argument, callback); }, argument); } _wrapOperation(apiCall, settings, argument, callback) { // TODO: this code defies all logic, and just can't be accurate. // tslint:disable-next-line no-any let backoffSettings = settings.longrunning; if (!backoffSettings) { backoffSettings = gax_1.createBackoffSettings(100, 1.3, 60000, null, null, null, null); } const longrunningDescriptor = this.longrunningDescriptor; return apiCall(argument, (err, rawResponse) => { if (err) { callback(err, null, rawResponse); return; } const operation = new Operation(rawResponse, longrunningDescriptor, backoffSettings, settings); callback(null, operation, rawResponse); }); } } exports.LongrunningApiCaller = LongrunningApiCaller; class Operation extends events_1.EventEmitter { /** * Wrapper for a google.longrunnung.Operation. * * @constructor * * @param {google.longrunning.Operation} grpcOp - The operation to be wrapped. * @param {LongrunningDescriptor} longrunningDescriptor - This defines the * operations service client and unpacking mechanisms for the operation. * @param {BackoffSettings} backoffSettings - The backoff settings used in * in polling the operation. * @param {CallOptions=} callOptions - CallOptions used in making get operation * requests. */ constructor(grpcOp, longrunningDescriptor, backoffSettings, callOptions) { super(); this.completeListeners = 0; this.hasActiveListeners = false; this.latestResponse = grpcOp; this.longrunningDescriptor = longrunningDescriptor; this.result = null; this.metadata = null; this.backoffSettings = backoffSettings; this._unpackResponse(grpcOp); this._listenForEvents(); this._callOptions = callOptions; } /** * Begin listening for events on the operation. This method keeps track of how * many "complete" listeners are registered and removed, making sure polling * is handled automatically. * * As long as there is one active "complete" listener, the connection is open. * When there are no more listeners, the polling stops. * * @private */ _listenForEvents() { this.on('newListener', event => { if (event === 'complete') { this.completeListeners++; if (!this.hasActiveListeners) { this.hasActiveListeners = true; this.startPolling_(); } } }); this.on('removeListener', event => { if (event === 'complete' && --this.completeListeners === 0) { this.hasActiveListeners = false; } }); } /** * Cancels current polling api call and cancels the operation. * * @return {Promise} the promise of the OperationsClient#cancelOperation api * request. */ cancel() { if (this.currentCallPromise_) { this.currentCallPromise_.cancel(); } const operationsClient = this.longrunningDescriptor.operationsClient; return operationsClient.cancelOperation({ name: this.latestResponse.name }); } getOperation(callback) { const self = this; const operationsClient = this.longrunningDescriptor.operationsClient; function promisifyResponse() { if (!callback) { // tslint:disable-next-line variable-name const PromiseCtor = self._callOptions.promise; return new PromiseCtor((resolve, reject) => { if (self.latestResponse.error) { const error = new GoogleError_1.GoogleError(self.latestResponse.error.message); error.code = self.latestResponse.error.code; reject(error); } else { resolve([self.result, self.metadata, self.latestResponse]); } }); } return; } if (this.latestResponse.done) { this._unpackResponse(this.latestResponse, callback); return promisifyResponse(); } this.currentCallPromise_ = operationsClient.getOperation({ name: this.latestResponse.name }, this._callOptions); const noCallbackPromise = this.currentCallPromise_.then(responses => { self.latestResponse = responses[0]; self._unpackResponse(responses[0], callback); return promisifyResponse(); }); if (!callback) { return noCallbackPromise; } } _unpackResponse(op, callback) { const responseDecoder = this.longrunningDescriptor.responseDecoder; const metadataDecoder = this.longrunningDescriptor.metadataDecoder; let response; let metadata; if (op.done) { if (op.result === 'error') { const error = new GoogleError_1.GoogleError(op.error.message); error.code = op.error.code; if (callback) { callback(error); } return; } if (responseDecoder && op.response) { response = responseDecoder(op.response.value); this.result = response; } } if (metadataDecoder && op.metadata) { metadata = metadataDecoder(op.metadata.value); this.metadata = metadata; } if (callback) { callback(null, response, metadata, op); } } /** * Poll `getOperation` to check the operation's status. This runs a loop to * ping using the backoff strategy specified at initialization. * * Note: This method is automatically called once a "complete" event handler * is registered on the operation. * * @private */ startPolling_() { const self = this; let now = new Date(); const delayMult = this.backoffSettings.retryDelayMultiplier; const maxDelay = this.backoffSettings.maxRetryDelayMillis; let delay = this.backoffSettings.initialRetryDelayMillis; let deadline = Infinity; if (this.backoffSettings.totalTimeoutMillis) { deadline = now.getTime() + this.backoffSettings.totalTimeoutMillis; } let previousMetadataBytes; if (this.latestResponse.metadata) { previousMetadataBytes = this.latestResponse.metadata.value; } // tslint:disable-next-line no-any function emit(event, ...args) { self.emit(event, ...args); } function retry() { if (!self.hasActiveListeners) { return; } if (now.getTime() >= deadline) { const error = new GoogleError_1.GoogleError('Total timeout exceeded before any response was received'); error.code = grpc_1.status.DEADLINE_EXCEEDED; setImmediate(emit, 'error', error); return; } self.getOperation((err, result, metadata, rawResponse) => { if (err) { setImmediate(emit, 'error', err); return; } if (!result) { if (rawResponse.metadata && (!previousMetadataBytes || !rawResponse.metadata.value.equals(previousMetadataBytes))) { setImmediate(emit, 'progress', metadata, rawResponse); previousMetadataBytes = rawResponse.metadata.value; } // special case: some APIs fail to set either result or error // but set done = true (e.g. speech with silent file). // Don't hang forever in this case. if (rawResponse.done) { const error = new GoogleError_1.GoogleError('Long running operation has finished but there was no result'); error.code = grpc_1.status.UNKNOWN; setImmediate(emit, 'error', error); return; } setTimeout(() => { now = new Date(); delay = Math.min(delay * delayMult, maxDelay); retry(); }, delay); return; } setImmediate(emit, 'complete', result, metadata, rawResponse); }); } retry(); } /** * Wraps the `complete` and `error` events in a Promise. * * @return {promise} - Promise that resolves on operation completion and rejects * on operation error. */ promise() { // tslint:disable-next-line variable-name const PromiseCtor = this._callOptions.promise; return new PromiseCtor((resolve, reject) => { this.on('error', reject) .on('complete', (result, metadata, rawResponse) => { resolve([result, metadata, rawResponse]); }); }); } } exports.Operation = Operation; /** * Method used to create Operation objects. * * @constructor * * @param {google.longrunning.Operation} op - The operation to be wrapped. * @param {LongrunningDescriptor} longrunningDescriptor - This defines the * operations service client and unpacking mechanisms for the operation. * @param {BackoffSettings} backoffSettings - The backoff settings used in * in polling the operation. * @param {CallOptions=} callOptions - CallOptions used in making get operation * requests. */ function operation(op, longrunningDescriptor, backoffSettings, callOptions) { return new Operation(op, longrunningDescriptor, backoffSettings, callOptions); } exports.operation = operation; //# sourceMappingURL=longrunning.js.map