-
Notifications
You must be signed in to change notification settings - Fork 616
Return Jobs/Operations directly from the method that starts them #1306
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Comments
I think if the operation/job was responsible for creating something (like a VM) that the created VM would be emitted via I forgot about the previously existing compute.createVM('vm-name')
.on('error', function(err) {})
.on('created', function(apiResponse) {})
.on('running', function(operationMetadata) {})
.on('complete', function(vm, operationMetadata) {}); edit: created is bad too, haha, my naming abilities are failing me today |
var operation = compute.createVM('vm-name')
.on('error', function(err) {}) // the "POST /vm-name" call failed OR the operation failed
.on('running', function() {}) // arg removed. `operation.metadata` can be checked for the status
.on('complete', function(vm) {}); // took out `operationMetadata`. they can check `operation.metadata` for the status |
I agree, |
I know, I think it's just unnecessary. Please let me know what you think about the example in the last post. I've removed all metadata like arguments from the events themselves. |
That could work, I'm not a huge fan of making the initial apiResponse completely inaccessible though. Unrelated: Does this have any effect on |
If it's an error, var operation = zone.createVM('vm-name');
operation.metadata === apiResponse; // obviously not immediately, we emit `running` but no potentially stale "status" of the operation
Hmm, good thought. Let's see... var operation = vm.get({ autoCreate: true });
// the "GET /vm-name" call failed OR the "POST /vm-name" call failed OR the operation failed
operation.on('error', function(err) {});
// The VM is being created -- not emitted unless we have to create it
operation.on('running', function() {});
// The VM was either created or it already existed. Return the object here
operation.on('complete', function(vm) {}); Wdyt? |
It does seem like we're stepping on the toes of the future support of Promises... should we wait to tackle this then? |
Probably a good idea |
@callmehiphop do you think we should support this in 1.0 or wait until after, at the cost of breaking the API and forcing a 2.0? |
I like the idea of supporting it in 1.0 personally, we're already going to be breaking quite a bit with promises, so it might be a good idea to do it now and not make users have to refactor their code a second time when we do get around to it. |
I agree that this is a pretty broad pattern, so I'd vote we keep this for 1.0 rather than 2.0. I'm also OK with error, running, and complete. The Operation itself only has a boolean done state, so this seems reasonable. To translate this to promises, we'd only handle error and complete (as then, and catch) right? |
Yep! |
I'm fine with doing this, as long as there's still a way for the user to get back the operation (and not wait for it to complete). I can see scenarios where one would want to start an operation in one place, and wait for it to complete elsewhere (or not wait for it to complete at all). |
Very true. I think that gives us two options...
|
This could work, we'd need to come up with a consistent naming scheme.
I don't think this would work as the promise would still have the timer that's waiting for the operation to complete. The user needs a way to create the operation without google-cloud-node adding a listener for the e.g. // Current behavior
speech.startRecognition(...)
.then((operation) => { ... });
// Behavior proposed by this PR
speech.startRecognition(...)
.then((transcription) => { ... });
// Option 1: Create operation without waiting, via option
speech.startRecognition(..., { wait: false })
.then((operation) => { ... });
// Option 2: Create operation without waiting, via different method
speech.startRecognitionNoWait(...)
.then((operation) => { ... }); |
Given that req, I think one new method that returns an operation that doesn't start an interval is the cleanest and clearest separation. |
Hmm, this is interesting. For the Speech API, the current method name is |
Using the speech example... how about. // I'd just like to do this the regular way...
speech.recognize(...)
.then((transcript) => { ... });
// I don't want to wait. Give me the LRO please...
speech.recognize(...)
.on('start', (operation) => { ... });
// I want the final result, but I also want the operation....
speech.recognize(...)
.on('start', (operation) => { ... })
.then((transcript) => { ... });
// The same thing written slightly differently...
speech.recognize(...)
.on('start', (operation) => { ... })
.on('success', (operation) => { ... }); I feel like "beginning the work" is an event tied to the technical implementation. Resolving the promise is about completing my "business-level goal" of getting back a transcript of the audio I just shipped up "to the cloud". In other words... we return a promise (which is an event emitter that happens to spit out either 'success' or 'error'), but this promise also emits other events that are relevant to the underlying implementation details (in this case, "your work has started" via the 'start' event). Source: https://gist.github.com/dmvaldman/12a7e46be6c3097aae31 |
@callmehiphop is this feasible to implement? I stumbled on this, which might help: https://github.com/59naga/carrack (via https://npms.io/search?q=promise+eventemitter) |
I don't see how it's feasible to offer both behaviors without doing it via separate synchronous code paths. The user wants to pick one or the other—start the timer for me (convenience https://github.com/59naga/carrack won't help, as it solves a problem where the timer is always started. If the user wants the operation, then I also don't think we should tell the user to drop to a lower level of abstraction (gapic) to get to the API's default behavior (no timer). We should preserve existing behavior while also adding the new behavior, achieving it via different synchronous code paths (put it behind an option or a new method). *The method has to know synchronously whether to start the timer or not (and when it should resolve the promise), it cannot wait for the user to call |
Personally I'm not huge into mixing interfaces like that, I would vote on offering two different methods. I also like the way we handle My naming could use some work but here's a simple example. // user doesn't care when the op ends
compute.startCreateVM();
// user cares about the op
var operation = compute.startCreateVM()
.on('error', console.error)
.on('whatever', console.log)
.on('complete', function(vm) {});
// user just cares about when op is over
compute.createVM().then(function(vm) {});
// re using existing operation?
compute.createVM({ operationId: operation.id }).then(function(vm) {}); |
That doesn't sound right, but let me know if I'm mistaken: Zone.prototype.createVM = function() {
console.log('making the request...')
return this.request(...)
// ...
}
var operation = zone.createVM();
// => "making the request..." So if the request can be made when someone calls // Calling this method makes the API request to create an instance
var operation = zone.createVM();
// Returns the API response from the VM being created
// The operation hasn't started an interval
operation.on('response', function(apiResponse) {});
// Now the operation will start an interval
// This callback executes when the operation is complete
operation.then(function() {
// VM created
}); Basically, if it's possible to distinguish between when a user registers an event handler on If not, separate methods are fine with me. |
On trying to watch for On trying to watch for
I don't see how @callmehiphop's examples, slightly amended: // user just wants to enqueue the operation
compute.startCreateVM();
// user cares about the operation
compute.startCreateVM().then((operation) => {
console.log(operation.id); // id was provided by response from initial API call
operation
.on('error', (err) => {
// error during operation, i.e. server error
})
.on('complete', (vm) => {});
}, (err) => {
// failed to start operation, i.e. bad argument
});
// user just cares about the result of the operation
compute.createVM().then((vm) => { ... }, (err) => {
// err could be from initial API call, e.g. failed to create operation
// or err could have happened during the operation itself
}); |
It wouldn't be synchronous, but using another example, what happens with a method that doesn't use an operation: Zone.prototype.getVMs = function() {
return this.request(...);
}
var getVMsPromise = zone.getVMs()
// was a request made, even though I didn't register a `then`? |
Regarding your first two points, I agree that offering dual-functionality from this single method doesn't sound great, but wanted to see if a solution is in fact available, given @jgeewax's original proposal. From a technical standpoint, it might not be as sickening as overriding Zone.prototype.createVM = function() {
var requestPromise = this.request(...)
requestPromise.on('newListener', function (name) {
if (name === 'then') // resolve with completed operation
else if (name === 'response') // resolve with api response
})
return requestPromise
} Just putting this out there in case you guys know if that's how it works. If not, I definitely don't want to override |
After some thinking, a more simple and obvious solution presented itself, implemented in #1689, which fits in nicely with the chained nature of promises. Feel free to re-open this issue if more discussion is needed. |
RE: #1294 (comment)
Blocked
Let's wait for our support of promises to engineer this.
Before:
After:
@callmehiphop What should we do in these situations (regarding the second argument,
vm
):Emit
vm
withrunning
?... or is that getting weird?
The text was updated successfully, but these errors were encountered: