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
Service Registration: Cleanup of service.js #8698
Conversation
export function fromClass(win, id, constructor) { | ||
win = getTopWindow(win); | ||
return getServiceInternal(win, win, id, constructor); | ||
if (!isServiceRegistered(win, id)) { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The existence of opt_factory
here and in getServiceForDoc
breaks the registration pattern. What do you think we should do about this?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The current behavior is far from ideal: if you call getService with a factory but the service is already registered then the factory is ignored.
I can look into removing them, but I would rather not add that to this PR.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
if you call getService with a factory but the service is already registered then the factory is ignored.
No different from trying to register a service twice, right?
I would rather not add that to this PR.
That's fine, just thinking about how to make this API simpler.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
No different from trying to register a service twice, right?
Right, and incidentally this is how these functions worked before the service registration PRs. The recent changes just make this more apparent.
That's fine, just thinking about how to make this API simpler.
Gotcha. I will definitely followup after this.
src/service.js
Outdated
* @param {string} id | ||
* @return {?Object} | ||
*/ | ||
function getExistingServiceForEmbedWinOrNull(embedWin, id) { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Can you undo the change in position for these methods? It makes the diff harder to read.
In general, recommend minimizing diffs in sensitive changes like this PR.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
done
src/service.js
Outdated
* @return {Object} | ||
* @template T | ||
*/ | ||
function getServiceInternal(holder, context, id) { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Ditto re: position.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
''
if (s.build) { | ||
s.obj = s.build(); | ||
s.resolve(s.obj); | ||
} |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Removed because this is already done in getServicePromiseOrNullInternal
?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Yes. getServicePromiseOrnullInternal
should only return null if the service has never been registered. This means that there won't be an existing service.
src/service.js
Outdated
/** | ||
* @param {!Window} embedWin | ||
* @param {string} id | ||
* @return {?Object} | ||
*/ | ||
function getLocalExistingServiceForEmbedWinOrNull(embedWin, id) { | ||
function getExistingServiceForEmbedWinOrNull(embedWin, id) { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Why remove the "Local" qualifier? Seems important vis-a-vis getExistingServiceInEmbedScope
since the former falls back to the top-level window.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I could go either way on this. I assumed that the distinction came form explicitly mentioning the embed window in the function name vs the other function which just mentioned a more nebulous 'scope'. I'll revert it.
id, | ||
constructor); | ||
const holder = getAmpdocServiceHolder(ampdoc); | ||
if (!isServiceRegistered(holder, id)) { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Update the documentation to state that the service will be registered with opt_factory
if unregistered.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
done.
src/service.js
Outdated
// The service may have been requested already, in which case we have a | ||
// pending promise we need to fulfill. | ||
if (s.resolve) { | ||
s.resolve(s.obj); | ||
if (s.promise && s.resolve) { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Is there an instance where s.resolve
is not null but s.promise
is?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
No, I just thought this would make the intention more explicit. (IIRC we were checking for both elsewhere as well) I'll update all other similar checks to only check for resolve
export function fromClass(win, id, constructor) { | ||
win = getTopWindow(win); | ||
return getServiceInternal(win, win, id, constructor); | ||
if (!isServiceRegistered(win, id)) { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
if you call getService with a factory but the service is already registered then the factory is ignored.
No different from trying to register a service twice, right?
I would rather not add that to this PR.
That's fine, just thinking about how to make this API simpler.
Thanks for reverting the positioning changes. Diff is much more readable now. |
src/service.js
Outdated
/* opt_ctor */ undefined, | ||
() => service); | ||
// Force service to build | ||
getServiceInternal(embedWin, embedWin, id); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This can be reverted. Let's address the future of the convenience builder separately.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I'd rather we didn't as reverting this means reverting getServiceInternal as well. The public interface nor its behavior should change due to this.
src/service.js
Outdated
} | ||
s.build = null; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Please add a unit test for this.
src/service.js
Outdated
if (s.resolve) { | ||
s.obj = s.build(); | ||
s.resolve(/** @type {!Object} */(s.obj)); | ||
s.build = null; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Ditto.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
line deleted
} | ||
|
||
|
||
/** | ||
* Returns a promise for service `id` if the service has been registered | ||
* on `holder`. |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Need to set s.build
to null in this method. And a unit test. 😄
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
routing all of this through getServiceInternal to avoid duplication
src/service.js
Outdated
// The service may have been requested already, in which case we have a | ||
// pending promise we need to fulfill. | ||
if (s.resolve) { | ||
s.resolve(s.obj); | ||
s.resolve(/** @type {!Object} */(s.obj)); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Can we get rid of these typecasts by marking s.build
correctly?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Looks like it can just be deleted.
src/service.js
Outdated
s.obj = obj; | ||
s.resolve(obj); | ||
if (s.resolve) { | ||
s.obj = s.build(); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Just call getServiceInternal
?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
good idea
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Now that you're familiar with the service.js
functions, are there any duplicated ones that we can get rid of? Some of them seem very similar.
src/service.js
Outdated
@@ -596,8 +512,9 @@ function getServicePromiseOrNullInternal(holder, id) { | |||
} else { | |||
dev().assert(s.build, |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Isn't this handled by getServiceInternal
?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
We can get rid of it
@@ -596,8 +512,9 @@ function getServicePromiseOrNullInternal(holder, id) { | |||
} else { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Can we collapse the previous block into this one?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
sure
src/service.js
Outdated
// The service may have been requested already, in which case we have a | ||
// pending promise we need to fulfill. | ||
if (s.resolve) { | ||
s.resolve(s.obj); | ||
s.resolve((s.obj)); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Nit.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
thanks
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
@jridgewell This PR set out to remove quite a few redundant functions. I think there's more work to do eliminating calls to getService
that provide a factory, but I'm not sure how much cleaner this can get. We need to worry about
- services on a window vs services on an ampdoc
- embedded services vs top-level services
- services that can be returned directly vs services that need to be resolved by promise
- functions that get a service or service promise and expect them to exist vs functions that get a service or service promise if one exists and return null otherwise
We could probably clean those up some more, but I'm not sure how much more we could do without (further) altering the interface.
src/service.js
Outdated
// The service may have been requested already, in which case we have a | ||
// pending promise we need to fulfill. | ||
if (s.resolve) { | ||
s.resolve(s.obj); | ||
s.resolve((s.obj)); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
thanks
src/service.js
Outdated
@@ -596,8 +512,9 @@ function getServicePromiseOrNullInternal(holder, id) { | |||
} else { | |||
dev().assert(s.build, |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
We can get rid of it
@@ -596,8 +512,9 @@ function getServicePromiseOrNullInternal(holder, id) { | |||
} else { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
sure
src/service.js
Outdated
s.obj = obj; | ||
s.resolve(obj); | ||
if (s.resolve) { | ||
// getServiceInternal will resolve the project. |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
"resolve the promise."
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
whoops, thanks
test/functional/test-analytics.js
Outdated
fromClassForDoc( | ||
ampdoc, 'amp-analytics-instrumentation', MockInstrumentation); | ||
registerServiceBuilderForDoc( | ||
ampdoc, 'amp-analytics-instrumentation', MockInstrumentation); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Nit: +2 spaces
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
done
getService(window, 'a'); | ||
expect(window.services['a'].obj).to.not.be.null; | ||
expect(window.services['a'].build).to.be.null; | ||
}); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Thanks!
src/service.js
Outdated
s.obj = s.build(); | ||
return s.promise = Promise.resolve(s.obj); | ||
if (!s.obj) { | ||
// Instantiate service |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I think we can remove the conditional and change this comment to "Instantiate service if not already instantiated."
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
good idea
/** | ||
* Returns a service for the given id and ampdoc (a per-ampdoc singleton). | ||
* If the service is not yet available the factory function is invoked and | ||
* expected to return the service. | ||
* @param {!Node|!./service/ampdoc-impl.AmpDoc} nodeOrDoc | ||
* @param {string} id of the service. | ||
* @param {function(!./service/ampdoc-impl.AmpDoc):T=} opt_factory | ||
* Should create the service if it does not exist yet. If the factory is | ||
* not given, it is an error if the service does not exist yet. | ||
* Factory to create the service if the service does not exist yet. |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
So, we now have getExistingService*
for window and embed scope, but not for doc scope? Does this help to conflate registration and use-service cases/APIs?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I noticed this too but looks like performanceForOrNull
uses it. If those calls can be changed to performanceFor
, then we can remove the getExisting...
methods altogether.
Recommend making any additional changes after this PR though.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
There are getExistingService*orNull
functions, but I've completely removed functions that will fail an assertion if a service doesn't already exist as getServiceInternal
now handles that case.
There may not be some forDoc
ones as they weren't needed anywhere. I can add some in if you'd like.
As for performaneForOrNull
, my understanding is that there are times we don't expect the performance service to exist. If that's not the case then we can remove performanceForOrNull
as @choumx suggested.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Yeah, I think performanceForOrNull
is used as you describe and thus it's needed. I mostly reacted to absence of getExistingServiceForDoc
because I thought we were trying to remove API confusion between service registration and service use. E.g. Viewer is registered only once, but is being used via viewerFor
all over the place.
But, I'm ok if we reconsider this in a separate PR.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Callers shouldn't need to worry about registered vs built. I removed those functions since they were redundant with changes made to getServiceInternal
.
@choumx @dvoytenko @jridgewell Thank you all for the very thorough reviews. |
Now that no services use the
fromClass
andfromClassForDoc
codepaths, service.js can be cleaned up a bit. This PR:fromClass
andfromClassForDoc
. The same functionality is provided byregisterService
+getService
andregisterServiceForDoc
+getServiceForDoc
getExistingService{,forDoc}
methods whose functionality is provided by asserts elsewhere.getExistingServiceForWindowOrNull
has been renamed to be consistent with other function names/to @choumx @dvoytenko
Closes #4986