mirror of
https://github.com/odoo/owl.git
synced 2025-10-06 19:59:41 +07:00
[IMP] component: re-introduce error handling in lifecycle
This commit is contained in:
committed by
Aaron Bohy
parent
bca6afeb90
commit
e0c0306acd
@@ -10,6 +10,7 @@ import {
|
||||
RootFiber,
|
||||
__internal__destroyed,
|
||||
} from "./fibers";
|
||||
import { handleError, fibersInError } from "./error_handling";
|
||||
import { applyDefaultProps } from "./props_validation";
|
||||
import { STATUS } from "./status";
|
||||
import { applyStyles } from "./style";
|
||||
@@ -41,7 +42,15 @@ export function component(
|
||||
node.updateAndRender(props, parentFiber);
|
||||
} else {
|
||||
// new component
|
||||
const C = isDynamic ? name : parent.constructor.components[name as any];
|
||||
let C;
|
||||
if (isDynamic) {
|
||||
C = name;
|
||||
} else {
|
||||
C = parent.constructor.components[name as any];
|
||||
if (!C) {
|
||||
throw new Error(`Cannot find the definition of component "${name}"`);
|
||||
}
|
||||
}
|
||||
node = new ComponentNode(C, props, ctx.app, ctx);
|
||||
ctx.children[key] = node;
|
||||
|
||||
@@ -112,23 +121,27 @@ export class ComponentNode<T extends typeof Component = any> implements VNode<Co
|
||||
fiber.root.mounted.push(fiber);
|
||||
}
|
||||
const component = this.component;
|
||||
const prom = Promise.all(this.willStart.map((f) => f.call(component)));
|
||||
await prom;
|
||||
try {
|
||||
await Promise.all(this.willStart.map((f) => f.call(component)));
|
||||
} catch (e) {
|
||||
handleError(this, e);
|
||||
return;
|
||||
}
|
||||
if (this.status === STATUS.NEW && this.fiber === fiber) {
|
||||
this._render(fiber);
|
||||
}
|
||||
}
|
||||
|
||||
async render() {
|
||||
if (this.fiber && !this.fiber.bdom) {
|
||||
return this.fiber.root.promise;
|
||||
let fiber = this.fiber;
|
||||
if (fiber && !fiber.bdom && !fibersInError.has(fiber)) {
|
||||
return fiber.root.promise;
|
||||
}
|
||||
if (!this.bdom && !this.fiber) {
|
||||
// should find a way to return the future mounting promise
|
||||
return;
|
||||
}
|
||||
|
||||
const fiber = makeRootFiber(this);
|
||||
fiber = makeRootFiber(this);
|
||||
this.app.scheduler.addFiber(fiber);
|
||||
await Promise.resolve();
|
||||
if (this.status === STATUS.DESTROYED) {
|
||||
@@ -143,15 +156,10 @@ export class ComponentNode<T extends typeof Component = any> implements VNode<Co
|
||||
_render(fiber: Fiber | RootFiber) {
|
||||
try {
|
||||
fiber.bdom = this.renderFn();
|
||||
fiber.root.counter--;
|
||||
} catch (e) {
|
||||
fiber.root.error = e;
|
||||
this.handleError(fiber);
|
||||
handleError(this, e);
|
||||
}
|
||||
fiber.root.counter--;
|
||||
}
|
||||
|
||||
handleError(fiber: Fiber) {
|
||||
fiber.node.app.destroy();
|
||||
}
|
||||
|
||||
destroy() {
|
||||
|
||||
@@ -0,0 +1,50 @@
|
||||
import type { ComponentNode } from "./component_node";
|
||||
import type { Fiber } from "./fibers";
|
||||
|
||||
export const fibersInError: WeakMap<Fiber, Error> = new WeakMap();
|
||||
export const nodeErrorHandlers: WeakMap<ComponentNode, ((error: Error) => void)[]> = new WeakMap();
|
||||
|
||||
function _handleError(node: ComponentNode | null, error: Error): boolean {
|
||||
if (!node) {
|
||||
return false;
|
||||
}
|
||||
const fiber = node.fiber;
|
||||
if (fiber) {
|
||||
fibersInError.set(fiber, error);
|
||||
}
|
||||
|
||||
const errorHandlers = nodeErrorHandlers.get(node);
|
||||
if (errorHandlers) {
|
||||
if (fiber && !fiber.children.length) {
|
||||
fiber.root.counter--;
|
||||
}
|
||||
|
||||
let propagate = true;
|
||||
for (const h of errorHandlers) {
|
||||
try {
|
||||
h(error);
|
||||
propagate = false;
|
||||
} catch (e) {
|
||||
error = e as Error;
|
||||
}
|
||||
}
|
||||
|
||||
if (propagate) {
|
||||
return _handleError(node.parent, error);
|
||||
}
|
||||
return true;
|
||||
} else {
|
||||
return _handleError(node.parent, error);
|
||||
}
|
||||
}
|
||||
|
||||
export function handleError(node: ComponentNode, error: Error) {
|
||||
const fiber = node.fiber!;
|
||||
fibersInError.set(fiber.root, error);
|
||||
|
||||
if (!_handleError(node, error)) {
|
||||
try {
|
||||
node.app.destroy();
|
||||
} catch (e) {}
|
||||
}
|
||||
}
|
||||
@@ -2,6 +2,7 @@ import type { BDom } from "../blockdom";
|
||||
import { mount } from "../blockdom";
|
||||
import type { ComponentNode } from "./component_node";
|
||||
import { STATUS } from "./status";
|
||||
import { fibersInError } from "./error_handling";
|
||||
// import { mountBlock } from "./bdom/block";
|
||||
|
||||
export function makeChildFiber(node: ComponentNode, parent: Fiber): Fiber {
|
||||
@@ -27,6 +28,10 @@ export function makeRootFiber(node: ComponentNode): Fiber {
|
||||
current.children = [];
|
||||
root.counter++;
|
||||
current.bdom = null;
|
||||
if (fibersInError.has(current)) {
|
||||
fibersInError.delete(current);
|
||||
fibersInError.delete(root);
|
||||
}
|
||||
return current;
|
||||
}
|
||||
const fiber = new RootFiber(node);
|
||||
@@ -81,7 +86,6 @@ export class Fiber {
|
||||
|
||||
export class RootFiber extends Fiber {
|
||||
counter: number = 1;
|
||||
error: Error | null = null;
|
||||
resolve: any;
|
||||
promise: Promise<any>;
|
||||
reject: any;
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
import { getCurrent } from "./component_node";
|
||||
import { nodeErrorHandlers } from "./error_handling";
|
||||
|
||||
// -----------------------------------------------------------------------------
|
||||
// hooks
|
||||
@@ -47,3 +48,15 @@ export function onRender(fn: () => void | any) {
|
||||
return renderFn();
|
||||
};
|
||||
}
|
||||
|
||||
export function onError(fn: (error: Error) => void | any) {
|
||||
const node = getCurrent()!;
|
||||
let handlers = nodeErrorHandlers.get(node);
|
||||
if (handlers) {
|
||||
handlers.push(fn);
|
||||
} else {
|
||||
handlers = [];
|
||||
handlers.push(fn);
|
||||
nodeErrorHandlers.set(node, handlers);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
import { Fiber, RootFiber } from "./fibers";
|
||||
import { handleError, fibersInError } from "./error_handling";
|
||||
import { STATUS } from "./status";
|
||||
|
||||
// -----------------------------------------------------------------------------
|
||||
@@ -46,20 +47,27 @@ export class Scheduler {
|
||||
this.tasks.delete(fiber);
|
||||
return;
|
||||
}
|
||||
if (fiber.error) {
|
||||
const hasError = fibersInError.has(fiber);
|
||||
if (hasError && fiber.counter !== 0) {
|
||||
this.tasks.delete(fiber);
|
||||
fiber.reject(fiber.error);
|
||||
fiber.reject(fibersInError.get(fiber));
|
||||
return;
|
||||
}
|
||||
if (fiber.node.status === STATUS.DESTROYED) {
|
||||
this.tasks.delete(fiber);
|
||||
return;
|
||||
}
|
||||
|
||||
if (fiber.counter === 0) {
|
||||
if (!fiber.error) {
|
||||
fiber.complete();
|
||||
if (!hasError) {
|
||||
try {
|
||||
fiber.complete();
|
||||
fiber.resolve();
|
||||
} catch (e) {
|
||||
handleError(fiber.node, e);
|
||||
fiber.reject(e);
|
||||
}
|
||||
}
|
||||
fiber.resolve();
|
||||
this.tasks.delete(fiber);
|
||||
}
|
||||
});
|
||||
|
||||
@@ -68,6 +68,7 @@ export {
|
||||
onPatched,
|
||||
onRender,
|
||||
onDestroyed,
|
||||
onError,
|
||||
} from "./component/lifecycle_hooks";
|
||||
|
||||
export const __info__ = {};
|
||||
|
||||
Reference in New Issue
Block a user