`DocHandle.value` and `DocHandle.doc` are confusing
alexjg opened this issue · comments
Currently the DocHandle.doc
attribute throws an exception when trying to access it before the document is ready, where ready means (as far as I can tell) one of:
- The handle was created wih
Repo.create()
- The handle was created with
Repo.find()
and we found something in storage - The handle was created with
Repo.find()
, nothing was found in storage but a peer sent us some changes for the document
The intention here is that the user either call DocHandle.isReady
before accessing DocHandle.doc
. There is also an async method DocHandle.value
which waits for the document to become ready and returns the value. This seems confusing to me because it's not clear from the naming or the types what the difference is between doc
and value()
and the relationship to isReady
is oblique.
I think a nicer API would be for DocHandle.doc
to return null
until the document is ready. For typescript users this will signal the difference between value()
and doc
in a way which hopefully will prompt reading the documentation for doc
, which should include a mention of isReady
and value()
.
Should be undefined
but yeah.
I believe we agreed to rename doc
to syncValue
(instead of renaming value()
to asyncValue()
). Or was it the other way? Here's a PR for the former. Certainly they should both be the same.
PS @acurrieclark this one will probably make you change your code, sorry.
See PR #125
Just been looking over the PR. I think that changing the return type of doc
to undefined
is a good one.
I would suggest that renaming is unnecessary here. If anything, i think this warrants an additional method:
handle.doc // => the actual underlying document. If it exists, it's defined
handle.value() => the resolved handle, which returns when ready
handle.syncValue() => returns the document if ready, errors if not
Having value()
as a method and syncValue
as a getter strikes me as counterintuitive.
The issue here is that value()
also returns the same thing as .doc
but has a different name. That's confusing. The .doc
value also always* exists and is non-null: we have to create it early in order to be able to start synchronizing.
My aim is to make the two names consistent. I would accept an argument that .value()
is wrong.
That said, I think you're right that a getter is also confusing/wrong. I'll update the patch to just make it a normal function. I have a real antipathy towards things that look like real values but aren't, it just didn't occur to me to change it at the time.
I guess here's the question: value
, or doc
?
Looking at other code and callbacks signatures, we use the term doc
everywhere else in the system (including internally.) This convinces me that the correct path is to call both doc
. The result will be:
async doc(): Promise<Automerge.Doc<T>>
and syncDoc(): Automerge.Doc<T> | undefined
Not to go round in circles, but now is probably the time to nail this down.
In my own codebase I am only ever using the async version to ensure that the handle is ready to be read from. I don't think I even use the return value.
My own preference would be that we just do away with a method which resolves to the doc value and replaced the whole shebang with something like:
await handle.isReady(); // <-- I know there is a sync `isReady()` right now, but this is more descriptive
doSomethingWithAReadyDoc(handle.doc)
I think that doc
is the most appropriate accessor, however it is used.
How about I add awaitReady()
to make it a bit more explicit as a pattern? isReady()
being sync, and awaitReady()
for async?
Is await awaitReady()
a little odd? Purely aesthetic I know.
yeah it is... Suggestions?
await untilReady()
?
I feel that we might be dancing around the fact that isReady()
is likely the most sensible choice?
If we used that, could we expose a state()
method to get the current state?
Well, given the advice for doc()
is to check isReady()
first...
That would still be the case!
Thinking about this, I feel like isReady()
doesn't imply blocking.