-
Notifications
You must be signed in to change notification settings - Fork 595
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
Returning arrays for everything makes for awkward and confusing code #2556
Comments
Do you have destructuring available in your code base? file.exists()
.then([exists] => {}) There is also The reason we always return an array is to be consistent, so you're not checking the docs for each method to determine which returns an array and which doesn't. We always provide the raw API response when it's available, because we can't be sure our simplified version is relevant to the user. We learned early on that deciding for them was a mistake, which is why it is always available from the same place and at the same time. For reference, here are related issues where this has been decided and discussed:
edit: updated example snippet |
We use async and await instead of callbacks. Having to write: const [exists] = await file.exists(name);
if (exists) {
// ...
} Is a lot more noise than: if (await file.exists(name)) {
// ...
} Yes we could do: if (await file.exists(name).then([exists] => exists)) But that's getting even less idiomatic. The guiding principles of promises + async/ await is that code should look like natural sync code with some await keywords sprinkled into it. In general the cloud apis feel awkward because of the arrays. This pattern doesn't match how the web platform or how node apis usually work. A more idiomatic pattern would have been to have separate apis for the lower level return types (exists and existsWithApi()), or have a separate callback for it (callback, lowLevelCallback), or have an argument that opts into getting the ApiResponse in addition to the simpler result (exists(..., {withApi: true}). As it stands right now we never want it in our code and need to work around it by building up wrapper libraries around cloud which is unfortunate. The common case definitely seems to be to not want the extra values, so you end up unwrapping [0] in 99% of cases. The principle we followed in the web apis was "allow the hard cases, favor the common ones". The current api honors the "allow the hard cases", but doesn't favor the common ones. Fwiw I totally understand the problem you're having here. We debated it lots too. :) (Anyway that's my 2c as someone who designed lots of the web platform's apis.) |
Thanks for the input and that motto is repeated throughout our discussions as well. In our case, we have repeatedly been a frustration for users when we do any level of hiding the raw data/not allowing raw input. While I might think users want the shorthand version, they actually seem to appreciate the full thing. I'm always interested in new solutions, however we are a bit past the point of being able to implement them, since we've reached GA with a few of our APIs. We would have to kick them to v2, which isn't impossible if we come up with something we all like. Separate APIs is probably my preferred route from your suggestions. We are introducing the concept of a "partial veneer" (i.e. a small helper interface on top of a generated API client). These always expose each and every raw API method there is, and the I/O to them is without any decoration or simplification. However, that won't be all of our APIs. Some need a thicker layer, like Datastore, Storage, and Spanner. They will have many custom methods, and duplicating them instead of just providing an extra argument feels heavy-handed. And accepting a flag that changes the response argument signature is a concept that gets a lot of rejection from my peers on this project. Just my thoughts, I'm curious what the others think. Let's re-open and see where we get! |
Before I say anything else, a thought about v2's timeline: It is considered a semver-major change to drop support for old versions of Node, and it is my desire to drop Node 4 when it goes EOL in April 2018. So, I am up for making a change like this in April, but not before that. (The leadup to that change, of course, could be significant.) I am on the record as believing that returning an array all the time was a mistake. I understand why it was done, but returning an object with meaningful keys in the cases where multiple items were returned would have made more sense. (In cases where multiple items of the same type were returned,
Yeah, we very much want to move away from this practice. We should accept full requests (usually) and return full responses (always).
What we can do in these cases is move (most of) them toward a partial Veneer model. I did this in Python for Pub/Sub, actually (!!). Realistically, most of the GAPIC methods in even Pub/Sub are entirely sufficient; it is only the banner methods like So, it might be reasonable to have Spanner v2 be a much, much thinner client, and only override things where we absolutely need to. Where that will not work well is APIs like BigQuery that have no GAPIC layer. There is also a question of time. Making this change would be pretty enormous, and will require keeping both versions of the codebase in sync from whenever we start (probably January or so) to April 2019, as we have to support the v1s for a year after v2 comes out. So, this is a significant undertaking if we decide to do it. |
Note that I do not want this to evolve into a "reinvent everything" discussion. It is worth getting ideas out, and then after that we need to close this issue. If we want to have a desired action item, I will have to see about getting it in the priority queue in early 2018. |
My two cents as a user, object instead of array would be nicer as is more readable |
@callmehiphop thoughts here would be great :) |
I think we could argue if objects or arrays are better all day, IMO with destructuring it doesn't really matter. Even if we returned objects we would still hit the same problems being described here. The real issue that needs to be discussed is how do we accommodate users who want slimmed down responses as well as users who don't want us to hide anything from them. I think the only reliable way to accomplish something like this would be to provide both a high and low level method for each RPC. |
That approach makes sense to me, unless there's something fancier we could do to avoid the headache, e.g.: const buckets = await storage.getBuckets()
const apiResponse = buckets[storage.API_RESPONSE_SYMBOL] // naming TBD In the case of Stream methods will always emit Hopefully I'm not too far off track with that thought process. |
IMO if using symbols work we should totally do it, however I am unsure if we can accommodate all RPCs using that method, although I would be fine with just not supporting it for certain methods (like |
Considering that most users probably wouldn't want the array behavior, what about adding some suffix to api calls for the fuller response? const googleAPI = require('some-google-library');
// for most users
if (await file.exists()) {
// ...
}
// for "power" users
const googleAPI = require('some-google-library');
const [exists, apiResponse] = await file.existsFull()
if (await file.exists()) {
// ...
} This is similar to how Bluebird.promisifyAll works by adding an |
For those interested, I want to lay out a plan of how we are thinking of approaching it. Thoughts welcomed!
Thoughts? @ofrobots @crwilcox @alexander-fenster @kinwa91 @stephenplusplus @callmehiphop |
SGTM, with the 'year' replaced with For clarity, it would be good to edit your post to clearly indicate which steps are semver majors. Is adding a |
My personal opinion, and precedent from Node.js core is that adding a |
@JustinBeckwith now that we aren't as scared of semver-major bumps anymore, does that change your preferred approach from your last suggestion? |
Closing this as a dup of #2896 🙃 |
Environment details
Steps to reproduce
google-cloud
ex. Almost every API looks like this:
but that means you end up doing [0] all over the place. The extra ApiResponse should be returned through some out of band system (ex. another argument that requests it, or a separate callback) instead of in an array with every response (and sometimes it seems there's an array even when there is no other data returned.
I end up having to write wrappers around every Google Cloud API function like this:
since doing:
is hard to read and confusing. In general all the arrays all over the API don't feel like idiomatic Promise code. You end up leaving comments all over explaining what the arrays are about.
The text was updated successfully, but these errors were encountered: