-
Notifications
You must be signed in to change notification settings - Fork 153
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
Support callFunction API #850
Conversation
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.
Bikeshed: "Call function"? :)
To summarize some of the discussion from late last week with @choumx and @jridgewell, here are the next steps:
|
src/main-thread/commands/function.ts
Outdated
|
||
const index = fnCallCount; | ||
promiseMap[index] = { resolve, reject, promise }; | ||
return { promise, index: fnCallCount++ }; |
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.
Do we need to worry about overflow?
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 don't think so. The fnCallCount
is only used to correlate fn call requests and its associated response. Reusing an older index is fine as long as the reused index had actually completed.
It would technically be a bug if there were some super super long running task with index=0
and then while it were running, we launched Number.MAX_VALUE
other calls until overflow. And then the next call would overwrite the first one.
Seems okay to me?
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.
TIL js doesn't overflow integers! It rounds to either Number.MAX_VALUE
or to Infinity
.
For example, Number.MAX_VALUE + 1 === Number.MAX_VALUE
. For our use case I'd prefer it wrapped, but I also really dont see anyone hitting 9 quadrillion callFunction
requests
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.
Me neither but should be easy to wrap to 0 when we hit MAX_VALUE.
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
Coolness |
src/main-thread/commands/function.ts
Outdated
if (status === ResolveOrReject.RESOLVE) { | ||
promiseMap[index].resolve(JSON.parse(strings.get(value))); | ||
} else { | ||
promiseMap[index].reject(JSON.parse(strings.get(value))); |
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.
How is undefined
return value handled here? Are there other cases where JSON.parse can fail/throw?
Nit: Hoist the JSON.parse
bit.
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.
Answer: it breaks :). Solving it required a bit of a hack since StringContext
will return ''
in the case of a falsy value. Therefore I added a new hasIndex
method so we can distinguish between undefined and the empty string.
I've also added a test case for 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.
Did you push changes 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.
@choumx: can you merge? I don't have access |
Added you to the write permission group @samouri. |
worker | ||
.callFunction("tooCoolToExist") | ||
.catch((err) => console.error(err)); | ||
worker |
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 add an await example to this demo?
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 thing!
package.json
Outdated
@@ -1,6 +1,6 @@ | |||
{ | |||
"name": "@ampproject/worker-dom", | |||
"version": "0.24.0", | |||
"version": "0.25.0", |
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.
Do you need to manually update the version?
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.
how can I automatically update the version?
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 release script will do this automatically (check out RELEASING.md).
reject = rej; | ||
}); | ||
|
||
// Wraparound to 0 in case someone attempts to register over 9 quadrillion promises. |
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.
LOL. This is likely not necessary, but funny nonetheless.
TransferrableMutationType.FUNCTION_CALL, | ||
ResolveOrReject.REJECT, | ||
index, | ||
store(JSON.stringify(`[worker-dom]: Exported function "${fnIdentifier}" could not be found.`)), |
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 a reason we need to do this? Could we just reject with a generic error?
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 it more helpful to give the devs a useful error message?
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.
Discussed this in Chat. The current method has higher DevEx whereas not having a helpful error message would save bytes.
We can have the best of both worlds by eventually having two builds, one for dev and one for prod. For now I'd like to merge with the slightly higher DevEx option esp as we work on getting to the optimal solution.
I'm going to hold off on merging this until I land a fix for #872. Edit: nevermind. My gut says there is something wrong with my local setup since this built fine. Merging now |
summary
The ability to invoke arbitrary worker functions from the main thread and get the result would be useful to two AMP use-cases, Protocol Adapters and Allow real JS in amp-bind.
This PR exposes two new interfaces. A function named
callFunction
invoking functions on the worker, as well as a method for the worker code to mark which functions should be exposed to the main thread. I implement this by changing the return value fromupgradeElement
from a standard Worker, into a wrapper object. Notably, it still delegates theonerror
and.terminate()
function calls to the underlying worker.questions i'd like help with
filesize
requirements for amp versions. Is this intentional / should I add them?todo / next steps
cc @choumx @jridgewell