update core async node plugins to catch and handle errors#666
Conversation
|
/canary |
Codecov Report❌ Patch coverage is Additional details and impacted files@@ Coverage Diff @@
## main #666 +/- ##
==========================================
+ Coverage 90.05% 90.12% +0.07%
==========================================
Files 333 333
Lines 21018 21122 +104
Branches 2104 2107 +3
==========================================
+ Hits 18927 19037 +110
+ Misses 2073 2067 -6
Partials 18 18 ☔ View full report in Codecov by Sentry. 🚀 New features to boost your workflow:
|
5672460 to
5d62af8
Compare
| public let onAsyncNode: AsyncHook2<JSValue, JSValue> | ||
| public let onAsyncNodeError: Hook2<JSValue, JSValue> |
There was a problem hiding this comment.
Not a fan of this pattern, but I'm not sure the best forward here. Android handles these well and treats them properly as bail hooks with a BailResult and actual types to represent the input and output.
There was a problem hiding this comment.
- Do we have any documentation on bail hooks? I don't know what that is 😅 .
- It's pretty non-trivial to interpret
JSValues into actual values, which is I assume why we do it this way.- BUT I'm new and would love to know your thoughts on an alternative.
There was a problem hiding this comment.
- Do we have any documentation on bail hooks? I don't know what that is 😅 .
Not sure the extent to which this exists for mobile, but for web/core, the hooks use the tapable-ts library based on webpack/tapable, which documents the hook types nicely here.
- It's pretty non-trivial to interpret JSValues into actual values, which is I assume why we do it this way.
BUT I'm new and would love to know your thoughts on an alternative.
I guess using a JSValue isn't a huge issue, but it would be nice if we could use anything that can easily be encoded into and decoded from a JSValue and have the base hook type handle that conversion so that the API here is a little more obvious to the consumers what they are getting rather than a JSValue where they need to look at our docs to understand what the input here is.
These hooks also don't provide a type for the return type, so it isn't immediately obvious that you can return a value from these or what that return value should even look like.
The BailResult I mentioned would be a part of that return type for BailHooks since those help indicate whether or not the result should cause the hook to bail or continue. This part might not be too big of a deal since the way the hooks work under the hood is that returning undefined will cause it to continue. Having a BailResult pattern may be helpful even in core though since we may want to enable cases where someone can both bail and return a result of undefined, but I think that warrants a larger discussion.
|
/canary |
2011e24 to
bb2c9df
Compare
| do { | ||
| let result = try await hook(hookValue) | ||
| DispatchQueue.main.async { | ||
| resolve(result as Any) | ||
| } | ||
| } catch let e { | ||
| let message = e.playerDescription | ||
| reject("Async hook threw with error '\(message)'") |
There was a problem hiding this comment.
I tried to put this do { ... } catch { ... } in the JSUtilities.createPromise to reject on any error, but had issues where the error wasn't bubbling up, I imagine it has something to do with the Task wrapper here. I'm not sure if the createPromise util can account for that while still handling errors elsewhere so I left the error handling here.
If there is a better way to do this, let me know
|
/canary |
| const transform: BeforeTransformFunction = (asset) => { | ||
| const newAsset = asset.children?.[0]?.value; | ||
|
|
||
| if (!newAsset) { | ||
| return asyncTransform(asset.value.id, "collection"); | ||
| } | ||
| return asyncTransform(asset.value.id, "collection", newAsset); | ||
| }; | ||
|
|
||
| const transformPlugin = new AssetTransformCorePlugin( | ||
| new Registry([[{ type: "chat-message" }, { beforeResolve: transform }]]), | ||
| ); | ||
|
|
||
| class TestAsyncPlugin implements PlayerPlugin { | ||
| name = "test-async"; | ||
| apply(player: Player) { | ||
| player.hooks.view.tap("test-async", (view) => { | ||
| transformPlugin.apply(view); | ||
| }); | ||
| } | ||
| } |
There was a problem hiding this comment.
This might not be the best setup for these tests, but I needed to add these to keep the tests identical to how they functioned with the ReferenceAssetsPlugin.
There was a problem hiding this comment.
I think this is probably fine. Creating testing utilities for stuff like this is something we should make a ticket for on the backlog
5f0a4e6 to
16c00e5
Compare
|
/canary |
16bcebb to
2777574
Compare
|
IMO this should be a minor release just because its adds a new API but open to make it a patch if there are strong opinions. |
2777574 to
ad2cf3f
Compare
| import kotlinx.serialization.KSerializer | ||
| import kotlinx.serialization.Serializable | ||
|
|
||
| @Serializable(with = NodeSyncBailHook2.Serializer::class) |
There was a problem hiding this comment.
Out of curiosity, any reason we need to call this NodeSyncBailHook2?
There was a problem hiding this comment.
I was following the existing naming convention for NodeAsyncParallelBailHook2. I'm not familiar enough with Kotlin to know if there is a better way to share the class name as the number of type arguments grows :X
There was a problem hiding this comment.
Ah, I assumed it was just a re-declaration of what the underlying hook was.
There was a problem hiding this comment.
the number is just based on the number of args
| const transform: BeforeTransformFunction = (asset) => { | ||
| const newAsset = asset.children?.[0]?.value; | ||
|
|
||
| if (!newAsset) { | ||
| return asyncTransform(asset.value.id, "collection"); | ||
| } | ||
| return asyncTransform(asset.value.id, "collection", newAsset); | ||
| }; | ||
|
|
||
| const transformPlugin = new AssetTransformCorePlugin( | ||
| new Registry([[{ type: "chat-message" }, { beforeResolve: transform }]]), | ||
| ); | ||
|
|
||
| class TestAsyncPlugin implements PlayerPlugin { | ||
| name = "test-async"; | ||
| apply(player: Player) { | ||
| player.hooks.view.tap("test-async", (view) => { | ||
| transformPlugin.apply(view); | ||
| }); | ||
| } | ||
| } |
There was a problem hiding this comment.
I think this is probably fine. Creating testing utilities for stuff like this is something we should make a ticket for on the backlog
KVSRoyal
left a comment
There was a problem hiding this comment.
The code lgtm from an iOS perspective, in my limited knowledge 😅 . I had a few curiosity questions, but none about things to change.
| test_deps = [ | ||
| ":node_modules/@player-ui/reference-assets-plugin", | ||
| ":node_modules/@player-ui/check-path-plugin", | ||
| ":node_modules/@player-ui/partial-match-registry", |
There was a problem hiding this comment.
Curiosity question: What does the partial-match-registry do? Do we have docs on it?
There was a problem hiding this comment.
The partial-match-registry exports the classes that handle asset/transform matching. It lets you register an object to match on, typically something like { type: 'asset-type' } but can be as complex as you want and it will handle the logic around matching the asset props to the correct transform or asset.
It was brought in here in this case since I needed a way to remove the reference-assets-plugin as a test dependency in this package, but needed to use the same transform setup that it was getting from that plugin. You can see the use in index.test.ts
| public let onAsyncNode: AsyncHook2<JSValue, JSValue> | ||
| public let onAsyncNodeError: Hook2<JSValue, JSValue> |
There was a problem hiding this comment.
- Do we have any documentation on bail hooks? I don't know what that is 😅 .
- It's pretty non-trivial to interpret
JSValues into actual values, which is I assume why we do it this way.- BUT I'm new and would love to know your thoughts on an alternative.
|
|
||
| var count = 0 | ||
|
|
||
| let asyncNodePluginPlugin = AsyncNodePluginPlugin() |
There was a problem hiding this comment.
AsyncNodePluginPlugin is the worst name ever lol. Nothing you can do about it, but I think we should consider renaming that down the line. 😅
There was a problem hiding this comment.
We also have something along the line of BeaconPluginPlugin for plugins for the Beacon Plugin.
|
|
||
| if let pluginRef = pluginRef { | ||
| self.hooks = AsyncNodeHook(onAsyncNode: AsyncHook2(baseValue: pluginRef, name: "onAsyncNode")) | ||
| self.hooks = AsyncNodeHook(onAsyncNode: AsyncHook2(baseValue: pluginRef, name: "onAsyncNode"), onAsyncNodeError: Hook2(baseValue: pluginRef, name: "onAsyncNodeError")) |
There was a problem hiding this comment.
Do we have doc on how to use these hooks? 🤔 I don't see any on the docs site.
There was a problem hiding this comment.
There are some docs on the async stuff here and I extended it in this PR with an Error Handling section to cover the new hook. Take a look and let me know if there is anything helpful I can add
AsyncNodePluginto include a new hookonAsyncNodeErroronAsyncNodehook and allow for a fallback node to be inserted in case of an error.AsyncNodePluginPluginto call theonAsyncNodeErrorhook when it fails to handle the async node.onAsyncNodeErrordoes not return a new node ornull, then the error is used to fail the current player flow.//plugins/async-node/coreand//plugins/reference-assets/coreasync-nodeto the view that generated it to prevent re-use of the cache across views, flows or player instances.Change Type (required)
Indicate the type of change your pull request is:
patchminormajorN/ADoes your PR have any documentation updates?
Release Notes
AsyncNodePluginto include a new hookonAsyncNodeErrorfor catching errors triggered when resolving async nodes.//plugins/async-node/coreand//plugins/reference-assets/coreasync-nodeto the view that generated it to prevent re-use of the cache across views, flows or player instances.📦 Published PR as canary version:
0.11.3--canary.666.24188Try this version out locally by upgrading relevant packages to 0.11.3--canary.666.24188