Simplifying return objects for methods with network requests
See original GitHub issueConsider a simple case of reading and updating an item:
const item = await cosmos.item(id).read()
item.foo = "bar"
await cosmos.items.upsert(item)
While this API works for basic operations, it does not expose HTTP headers or any other raw request data. Many advanced uses of cosmos need this data. We currently solve this by returning a wrapper object for all methods with underlying network requests:
const { body: item, headers } = await cosmos.item(id).read()
item.foo = "bar"
await cosmos.items.upsert(item)
Unfortunately, this complicates the returned object and API for simple usage. Ideally we could support both use cases. Return objects could be simple but also provide metadata that advanced users need. With newer JavaScript features this may be possible. These are some approaches we are considering.
1: Use Symbol
Cosmos SDK can use a symbol to store data directly on the returned database object.
// Cosmos SDK Code
cosmos.headers = Symbol('headers')
// User code
const item = await cosmos.item(id).read()
const headers = item[cosmos.headers]
#34 contains some previous discussion of this approach.
Pros
- Getting headers looks just like normal object property access
- Symbols as keys allows us to add metadata without conflicting with user data. These keys are omitted when iterated or stringified so users can safely pass them back to cosmos without polluting data.
Cons
- Requires advanced users to be familiar with
Symbol
which is a relatively new JS language feature - Requires polyfill in IE
2: getHeaders
method
This is a twist on proposal 1. Symbols are still used as keys, but we expose a utility method so symbols are not exposed to user code.
// Cosmos SDK Code
const headersKey = Symbol('headers')
comsos.getHeaders = obj => obj[headersKey]
// User code
const item = await cosmos.item(id).read()
const headers = cosmos.getHeaders(item)
Pros
- Similar to apporach 1 but without exposing users to
Symbol
Cons
- Requires polyfill in IE
getHeaders
may look like magic to some users
3: Extend Promise
This approach returns a special class of promise that would allow users to control the return type.
// Cosmos SDK code
class CosmosRequestPromise extends Promise {
includeHeaders() { /* implementation ommited */ }
}
// User code
const item = await cosmos.item(id).read()
const { body: item, headers } = await cosmos.item(id).read().includeHeaders()
Pros
- Doesn’t use Symbol at all
Cons
- Requires creating and maintaining our own promise type
includeHeaders
may look like magic to some users- Implementation details are still unclear. This approach seems possible, but we haven’t built a POC.
Issue Analytics
- State:
- Created 5 years ago
- Comments:6 (4 by maintainers)
Top GitHub Comments
1 & 2 are compatible. 2 is effectively just a utility method for getting the right index.
Other advantages of doing 1 & 2 together is supporting some intelligent utility methods. Most folks don’t want the headers, they want a specific property from the headers such as RUs or activity id. We could have
client.getRUs
andclient.getActivityId
methods as well, which short cut and abstract away the details for folks.I’m considering using something similar in an graph database client library I use at work. But instead of symbols, a non enumerable field.
However we have a history of “private” underscored properties. I think a symbol works great!