Skip to content

Commit

Permalink
feat: deferred client initialization (#212)
Browse files Browse the repository at this point in the history
This PR includes changes from googleapis/gapic-generator-typescript#317
that will move the asynchronous initialization and authentication from the client constructor
to an `initialize()` method. This method will be automatically called when the first RPC call
is performed.

The client library usage has not changed, there is no need to update any code.

If you want to make sure the client is authenticated _before_ the first RPC call, you can do
```js
await client.initialize();
```
manually before calling any client method.
  • Loading branch information
gcf-merge-on-green[bot] authored Mar 6, 2020
1 parent b049bd3 commit 84d1c74
Show file tree
Hide file tree
Showing 5 changed files with 262 additions and 90 deletions.
124 changes: 81 additions & 43 deletions packages/google-cloud-scheduler/src/v1/cloud_scheduler_client.ts
Original file line number Diff line number Diff line change
Expand Up @@ -45,8 +45,13 @@ export class CloudSchedulerClient {
private _innerApiCalls: {[name: string]: Function};
private _pathTemplates: {[name: string]: gax.PathTemplate};
private _terminated = false;
private _opts: ClientOptions;
private _gaxModule: typeof gax | typeof gax.fallback;
private _gaxGrpc: gax.GrpcClient | gax.fallback.GrpcClient;
private _protos: {};
private _defaults: {[method: string]: gax.CallSettings};
auth: gax.GoogleAuth;
cloudSchedulerStub: Promise<{[name: string]: Function}>;
cloudSchedulerStub?: Promise<{[name: string]: Function}>;

/**
* Construct an instance of CloudSchedulerClient.
Expand All @@ -70,8 +75,6 @@ export class CloudSchedulerClient {
* app is running in an environment which supports
* {@link https://developers.google.com/identity/protocols/application-default-credentials Application Default Credentials},
* your project ID will be detected automatically.
* @param {function} [options.promise] - Custom promise module to use instead
* of native Promises.
* @param {string} [options.apiEndpoint] - The domain name of the
* API remote host.
*/
Expand Down Expand Up @@ -101,25 +104,28 @@ export class CloudSchedulerClient {
// If we are in browser, we are already using fallback because of the
// "browser" field in package.json.
// But if we were explicitly requested to use fallback, let's do it now.
const gaxModule = !isBrowser && opts.fallback ? gax.fallback : gax;
this._gaxModule = !isBrowser && opts.fallback ? gax.fallback : gax;

// Create a `gaxGrpc` object, with any grpc-specific options
// sent to the client.
opts.scopes = (this.constructor as typeof CloudSchedulerClient).scopes;
const gaxGrpc = new gaxModule.GrpcClient(opts);
this._gaxGrpc = new this._gaxModule.GrpcClient(opts);

// Save options to use in initialize() method.
this._opts = opts;

// Save the auth object to the client, for use by other methods.
this.auth = gaxGrpc.auth as gax.GoogleAuth;
this.auth = this._gaxGrpc.auth as gax.GoogleAuth;

// Determine the client header string.
const clientHeader = [`gax/${gaxModule.version}`, `gapic/${version}`];
const clientHeader = [`gax/${this._gaxModule.version}`, `gapic/${version}`];
if (typeof process !== 'undefined' && 'versions' in process) {
clientHeader.push(`gl-node/${process.versions.node}`);
} else {
clientHeader.push(`gl-web/${gaxModule.version}`);
clientHeader.push(`gl-web/${this._gaxModule.version}`);
}
if (!opts.fallback) {
clientHeader.push(`grpc/${gaxGrpc.grpcVersion}`);
clientHeader.push(`grpc/${this._gaxGrpc.grpcVersion}`);
}
if (opts.libName && opts.libVersion) {
clientHeader.push(`${opts.libName}/${opts.libVersion}`);
Expand All @@ -135,36 +141,38 @@ export class CloudSchedulerClient {
'protos',
'protos.json'
);
const protos = gaxGrpc.loadProto(
this._protos = this._gaxGrpc.loadProto(
opts.fallback ? require('../../protos/protos.json') : nodejsProtoPath
);

// This API contains "path templates"; forward-slash-separated
// identifiers to uniquely identify resources within the API.
// Create useful helper objects for these.
this._pathTemplates = {
jobPathTemplate: new gaxModule.PathTemplate(
jobPathTemplate: new this._gaxModule.PathTemplate(
'projects/{project}/locations/{location}/jobs/{job}'
),
locationPathTemplate: new gaxModule.PathTemplate(
locationPathTemplate: new this._gaxModule.PathTemplate(
'projects/{project}/locations/{location}'
),
projectPathTemplate: new gaxModule.PathTemplate('projects/{project}'),
projectPathTemplate: new this._gaxModule.PathTemplate(
'projects/{project}'
),
};

// Some of the methods on this service return "paged" results,
// (e.g. 50 results at a time, with tokens to get subsequent
// pages). Denote the keys used for pagination and results.
this._descriptors.page = {
listJobs: new gaxModule.PageDescriptor(
listJobs: new this._gaxModule.PageDescriptor(
'pageToken',
'nextPageToken',
'jobs'
),
};

// Put together the default options sent with requests.
const defaults = gaxGrpc.constructSettings(
this._defaults = this._gaxGrpc.constructSettings(
'google.cloud.scheduler.v1.CloudScheduler',
gapicConfig as gax.ClientConfig,
opts.clientConfig || {},
Expand All @@ -175,17 +183,35 @@ export class CloudSchedulerClient {
// of calling the API is handled in `google-gax`, with this code
// merely providing the destination and request information.
this._innerApiCalls = {};
}

/**
* Initialize the client.
* Performs asynchronous operations (such as authentication) and prepares the client.
* This function will be called automatically when any class method is called for the
* first time, but if you need to initialize it before calling an actual method,
* feel free to call initialize() directly.
*
* You can await on this method if you want to make sure the client is initialized.
*
* @returns {Promise} A promise that resolves to an authenticated service stub.
*/
initialize() {
// If the client stub promise is already initialized, return immediately.
if (this.cloudSchedulerStub) {
return this.cloudSchedulerStub;
}

// Put together the "service stub" for
// google.cloud.scheduler.v1.CloudScheduler.
this.cloudSchedulerStub = gaxGrpc.createStub(
opts.fallback
? (protos as protobuf.Root).lookupService(
this.cloudSchedulerStub = this._gaxGrpc.createStub(
this._opts.fallback
? (this._protos as protobuf.Root).lookupService(
'google.cloud.scheduler.v1.CloudScheduler'
)
: // tslint:disable-next-line no-any
(protos as any).google.cloud.scheduler.v1.CloudScheduler,
opts
(this._protos as any).google.cloud.scheduler.v1.CloudScheduler,
this._opts
) as Promise<{[method: string]: Function}>;

// Iterate over each of the methods that the service provides
Expand Down Expand Up @@ -214,9 +240,9 @@ export class CloudSchedulerClient {
}
);

const apiCall = gaxModule.createApiCall(
const apiCall = this._gaxModule.createApiCall(
innerCallPromise,
defaults[methodName],
this._defaults[methodName],
this._descriptors.page[methodName] ||
this._descriptors.stream[methodName] ||
this._descriptors.longrunning[methodName]
Expand All @@ -230,6 +256,8 @@ export class CloudSchedulerClient {
return apiCall(argument, callOptions, callback);
};
}

return this.cloudSchedulerStub;
}

/**
Expand Down Expand Up @@ -352,6 +380,7 @@ export class CloudSchedulerClient {
] = gax.routingHeader.fromParams({
name: request.name || '',
});
this.initialize();
return this._innerApiCalls.getJob(request, options, callback);
}
createJob(
Expand Down Expand Up @@ -383,10 +412,10 @@ export class CloudSchedulerClient {
* `projects/PROJECT_ID/locations/LOCATION_ID`.
* @param {google.cloud.scheduler.v1.Job} request.job
* Required. The job to add. The user can optionally specify a name for the
* job in [name][google.cloud.scheduler.v1.Job.name]. [name][google.cloud.scheduler.v1.Job.name] cannot be the same as an
* job in {@link google.cloud.scheduler.v1.Job.name|name}. {@link google.cloud.scheduler.v1.Job.name|name} cannot be the same as an
* existing job. If a name is not specified then the system will
* generate a random unique name that will be returned
* ([name][google.cloud.scheduler.v1.Job.name]) in the response.
* ({@link google.cloud.scheduler.v1.Job.name|name}) in the response.
* @param {object} [options]
* Call options. See {@link https://googleapis.dev/nodejs/google-gax/latest/interfaces/CallOptions.html|CallOptions} for more details.
* @returns {Promise} - The promise which resolves to an array.
Expand Down Expand Up @@ -430,6 +459,7 @@ export class CloudSchedulerClient {
] = gax.routingHeader.fromParams({
parent: request.parent || '',
});
this.initialize();
return this._innerApiCalls.createJob(request, options, callback);
}
updateJob(
Expand All @@ -454,18 +484,18 @@ export class CloudSchedulerClient {
/**
* Updates a job.
*
* If successful, the updated [Job][google.cloud.scheduler.v1.Job] is returned. If the job does
* If successful, the updated {@link google.cloud.scheduler.v1.Job|Job} is returned. If the job does
* not exist, `NOT_FOUND` is returned.
*
* If UpdateJob does not successfully return, it is possible for the
* job to be in an [Job.State.UPDATE_FAILED][google.cloud.scheduler.v1.Job.State.UPDATE_FAILED] state. A job in this state may
* job to be in an {@link google.cloud.scheduler.v1.Job.State.UPDATE_FAILED|Job.State.UPDATE_FAILED} state. A job in this state may
* not be executed. If this happens, retry the UpdateJob request
* until a successful response is received.
*
* @param {Object} request
* The request object that will be sent.
* @param {google.cloud.scheduler.v1.Job} request.job
* Required. The new job properties. [name][google.cloud.scheduler.v1.Job.name] must be specified.
* Required. The new job properties. {@link google.cloud.scheduler.v1.Job.name|name} must be specified.
*
* Output only fields cannot be modified using UpdateJob.
* Any value specified for an output only field will be ignored.
Expand Down Expand Up @@ -514,6 +544,7 @@ export class CloudSchedulerClient {
] = gax.routingHeader.fromParams({
'job.name': request.job!.name || '',
});
this.initialize();
return this._innerApiCalls.updateJob(request, options, callback);
}
deleteJob(
Expand Down Expand Up @@ -586,6 +617,7 @@ export class CloudSchedulerClient {
] = gax.routingHeader.fromParams({
name: request.name || '',
});
this.initialize();
return this._innerApiCalls.deleteJob(request, options, callback);
}
pauseJob(
Expand All @@ -611,9 +643,9 @@ export class CloudSchedulerClient {
* Pauses a job.
*
* If a job is paused then the system will stop executing the job
* until it is re-enabled via [ResumeJob][google.cloud.scheduler.v1.CloudScheduler.ResumeJob]. The
* state of the job is stored in [state][google.cloud.scheduler.v1.Job.state]; if paused it
* will be set to [Job.State.PAUSED][google.cloud.scheduler.v1.Job.State.PAUSED]. A job must be in [Job.State.ENABLED][google.cloud.scheduler.v1.Job.State.ENABLED]
* until it is re-enabled via {@link google.cloud.scheduler.v1.CloudScheduler.ResumeJob|ResumeJob}. The
* state of the job is stored in {@link google.cloud.scheduler.v1.Job.state|state}; if paused it
* will be set to {@link google.cloud.scheduler.v1.Job.State.PAUSED|Job.State.PAUSED}. A job must be in {@link google.cloud.scheduler.v1.Job.State.ENABLED|Job.State.ENABLED}
* to be paused.
*
* @param {Object} request
Expand Down Expand Up @@ -664,6 +696,7 @@ export class CloudSchedulerClient {
] = gax.routingHeader.fromParams({
name: request.name || '',
});
this.initialize();
return this._innerApiCalls.pauseJob(request, options, callback);
}
resumeJob(
Expand All @@ -688,10 +721,10 @@ export class CloudSchedulerClient {
/**
* Resume a job.
*
* This method reenables a job after it has been [Job.State.PAUSED][google.cloud.scheduler.v1.Job.State.PAUSED]. The
* state of a job is stored in [Job.state][google.cloud.scheduler.v1.Job.state]; after calling this method it
* will be set to [Job.State.ENABLED][google.cloud.scheduler.v1.Job.State.ENABLED]. A job must be in
* [Job.State.PAUSED][google.cloud.scheduler.v1.Job.State.PAUSED] to be resumed.
* This method reenables a job after it has been {@link google.cloud.scheduler.v1.Job.State.PAUSED|Job.State.PAUSED}. The
* state of a job is stored in {@link google.cloud.scheduler.v1.Job.state|Job.state}; after calling this method it
* will be set to {@link google.cloud.scheduler.v1.Job.State.ENABLED|Job.State.ENABLED}. A job must be in
* {@link google.cloud.scheduler.v1.Job.State.PAUSED|Job.State.PAUSED} to be resumed.
*
* @param {Object} request
* The request object that will be sent.
Expand Down Expand Up @@ -741,6 +774,7 @@ export class CloudSchedulerClient {
] = gax.routingHeader.fromParams({
name: request.name || '',
});
this.initialize();
return this._innerApiCalls.resumeJob(request, options, callback);
}
runJob(
Expand Down Expand Up @@ -816,6 +850,7 @@ export class CloudSchedulerClient {
] = gax.routingHeader.fromParams({
name: request.name || '',
});
this.initialize();
return this._innerApiCalls.runJob(request, options, callback);
}

Expand Down Expand Up @@ -857,10 +892,10 @@ export class CloudSchedulerClient {
* A token identifying a page of results the server will return. To
* request the first page results, page_token must be empty. To
* request the next page of results, page_token must be the value of
* [next_page_token][google.cloud.scheduler.v1.ListJobsResponse.next_page_token] returned from
* the previous call to [ListJobs][google.cloud.scheduler.v1.CloudScheduler.ListJobs]. It is an error to
* switch the value of [filter][google.cloud.scheduler.v1.ListJobsRequest.filter] or
* [order_by][google.cloud.scheduler.v1.ListJobsRequest.order_by] while iterating through pages.
* {@link google.cloud.scheduler.v1.ListJobsResponse.next_page_token|next_page_token} returned from
* the previous call to {@link google.cloud.scheduler.v1.CloudScheduler.ListJobs|ListJobs}. It is an error to
* switch the value of {@link google.cloud.scheduler.v1.ListJobsRequest.filter|filter} or
* {@link google.cloud.scheduler.v1.ListJobsRequest.order_by|order_by} while iterating through pages.
* @param {object} [options]
* Call options. See {@link https://googleapis.dev/nodejs/google-gax/latest/interfaces/CallOptions.html|CallOptions} for more details.
* @returns {Promise} - The promise which resolves to an array.
Expand Down Expand Up @@ -916,6 +951,7 @@ export class CloudSchedulerClient {
] = gax.routingHeader.fromParams({
parent: request.parent || '',
});
this.initialize();
return this._innerApiCalls.listJobs(request, options, callback);
}

Expand Down Expand Up @@ -948,10 +984,10 @@ export class CloudSchedulerClient {
* A token identifying a page of results the server will return. To
* request the first page results, page_token must be empty. To
* request the next page of results, page_token must be the value of
* [next_page_token][google.cloud.scheduler.v1.ListJobsResponse.next_page_token] returned from
* the previous call to [ListJobs][google.cloud.scheduler.v1.CloudScheduler.ListJobs]. It is an error to
* switch the value of [filter][google.cloud.scheduler.v1.ListJobsRequest.filter] or
* [order_by][google.cloud.scheduler.v1.ListJobsRequest.order_by] while iterating through pages.
* {@link google.cloud.scheduler.v1.ListJobsResponse.next_page_token|next_page_token} returned from
* the previous call to {@link google.cloud.scheduler.v1.CloudScheduler.ListJobs|ListJobs}. It is an error to
* switch the value of {@link google.cloud.scheduler.v1.ListJobsRequest.filter|filter} or
* {@link google.cloud.scheduler.v1.ListJobsRequest.order_by|order_by} while iterating through pages.
* @param {object} [options]
* Call options. See {@link https://googleapis.dev/nodejs/google-gax/latest/interfaces/CallOptions.html|CallOptions} for more details.
* @returns {Stream}
Expand All @@ -971,6 +1007,7 @@ export class CloudSchedulerClient {
parent: request.parent || '',
});
const callSettings = new gax.CallSettings(options);
this.initialize();
return this._descriptors.page.listJobs.createStream(
this._innerApiCalls.listJobs as gax.GaxCall,
request,
Expand Down Expand Up @@ -1096,8 +1133,9 @@ export class CloudSchedulerClient {
* The client will no longer be usable and all future behavior is undefined.
*/
close(): Promise<void> {
this.initialize();
if (!this._terminated) {
return this.cloudSchedulerStub.then(stub => {
return this.cloudSchedulerStub!.then(stub => {
this._terminated = true;
stub.close();
});
Expand Down
Loading

0 comments on commit 84d1c74

Please sign in to comment.