Skip to content

Commit

Permalink
Run all JsComponent interactions asynchronously
Browse files Browse the repository at this point in the history
Initialization, update and dispose of JsComponent
is now executed in the next event loop cycle - i.e.
after all other knockout components have been initialized.
The main reason for this is to NOT handle
exceptions from the JS component and avoid
crashing the entire page when one component fails to load.
  • Loading branch information
exyi committed Aug 1, 2023
1 parent f059d47 commit 2fd65a1
Show file tree
Hide file tree
Showing 5 changed files with 34 additions and 22 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -51,38 +51,48 @@ export default {
const value0 = valueAccessor()

let lastProps = getCurrentProps(value0)
var [module, componentF] = findComponent(value0.view, value0.name)
const component = componentF.create(
element,
lastProps,
value0.commands ?? {},
value0.templates ?? {},
setProps
)
let component: DotvvmJsComponent
// defer all operations with the component so that
// * exceptions don't break the whole page
// * exceptions normally break the debugger
// knockout.js "handles" all exceptions, so you'd only get a console message and have to enable "break on all exceptions" in order to debug the component
// * we get the synchronous initialization done faster...
defer(() => {
const [module, componentF] = findComponent(value0.view, value0.name)
component = componentF.create(
element,
lastProps,
value0.commands ?? {},
value0.templates ?? {},
setProps
)
})

function update() {
const value = valueAccessor()
const currentProps = getCurrentProps(value)

const toUpdate: { [key: string]: any } = {}
for (const [n, v] of Object.entries(currentProps)) {
if (lastProps[n] !== v) {
toUpdate[n] = v
defer(() => {
const toUpdate: { [key: string]: any } = {}
for (const [n, v] of Object.entries(currentProps)) {
if (lastProps[n] !== v) {
toUpdate[n] = v
}
}
if (keys(toUpdate).length > 0) {
component?.updateProps(toUpdate)
}
}
if (keys(toUpdate).length > 0) {
component.updateProps(toUpdate)
}

lastProps = currentProps
lastProps = currentProps
})
}
newState.subscribe(update)
// run update when something observable changes
let updaterComputed = ko.computed(() => update())
ko.utils.domNodeDisposal.addDisposeCallback(element, () => {
updaterComputed.dispose()
component.dispose()
newState.unsubscribe(update)
defer(() => component?.dispose?.())
})

// No need to evaluate data-bind attributes inside the component
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -30,7 +30,7 @@ export default {
}
metadata = (obs as any).dotvvmMetadata;
}
setTimeout(() => {
defer(() => {
// remove element from collection when its removed from dom
ko.utils.domNodeDisposal.addDisposeCallback(element, () => {
for (const meta of metadata) {
Expand All @@ -40,7 +40,7 @@ export default {
}
}
});
}, 0);
});

const valueUpdateHandler = () => {
const obs = valueAccessor();
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -31,6 +31,6 @@ export function runNextInQueue(queueName: string) {
const queue = getPostbackQueue(queueName);
if (queue.queue.length > 0) {
const callback = queue.queue.shift()!;
Promise.resolve().then(callback)
defer(callback)

Check failure on line 34 in src/Framework/Framework/Resources/Scripts/postback/queue.ts

View workflow job for this annotation

GitHub Actions / JS unit tests

ReferenceError: defer is not defined at runNextInQueue (/home/runner/work/dotvvm/dotvvm/src/Framework/Framework/Resources/Scripts/postback/queue.ts:34:9) at dispatchNext (/home/runner/work/dotvvm/dotvvm/src/Framework/Framework/Resources/Scripts/postback/internal-handlers.ts:112:23)

Check failure on line 34 in src/Framework/Framework/Resources/Scripts/postback/queue.ts

View workflow job for this annotation

GitHub Actions / JS unit tests

ReferenceError: defer is not defined at runNextInQueue (/home/runner/work/dotvvm/dotvvm/src/Framework/Framework/Resources/Scripts/postback/queue.ts:34:9) at dispatchNext (/home/runner/work/dotvvm/dotvvm/src/Framework/Framework/Resources/Scripts/postback/internal-handlers.ts:112:23)

Check failure on line 34 in src/Framework/Framework/Resources/Scripts/postback/queue.ts

View workflow job for this annotation

GitHub Actions / JS unit tests

ReferenceError: defer is not defined at runNextInQueue (/home/runner/work/dotvvm/dotvvm/src/Framework/Framework/Resources/Scripts/postback/queue.ts:34:9) at dispatchNext (/home/runner/work/dotvvm/dotvvm/src/Framework/Framework/Resources/Scripts/postback/internal-handlers.ts:112:23)
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -41,7 +41,7 @@ export function restoreUpdatedControls(resultObject: any, updatedControls: any)
} else {
updatedControl.parent.appendChild(element);
}
Promise.resolve().then(() => ko.applyBindings(updatedControl.dataContext, element))
defer(() => ko.applyBindings(updatedControl.dataContext, element))
}
}
}
Expand Down
2 changes: 2 additions & 0 deletions src/Framework/Framework/Resources/Scripts/utils/promise.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
/** Runs the callback in the next event loop cycle */
const defer: <T>(callback: () => T) => Promise<T> = Promise.resolve().then

0 comments on commit 2fd65a1

Please sign in to comment.