[FIX] component, fiber: error_handling at the Fiber level

Before this commit, errors triggered at the level of the fiber (as opposed to at the level
of a component's rendering), were handled as the very top level of the rendering, that is,
in the scheduler.
This was wrong because components below in the rendering tree would not have a chance to handle their
children's or their own errors.

After this commit, error triggered in willPatch, onMounted and onPatched are correctly handled
at the closest component to where they were thrown.
This commit is contained in:
Lucas Perais (lpe)
2021-11-17 11:06:24 +01:00
committed by Aaron Bohy
parent cb107cef7d
commit ed3e6dcbb6
5 changed files with 215 additions and 95 deletions
+14 -4
View File
@@ -1,5 +1,5 @@
import type { ComponentNode } from "./component_node";
import type { Fiber } from "./fibers";
import { Fiber } from "./fibers";
export const fibersInError: WeakMap<Fiber, Error> = new WeakMap();
export const nodeErrorHandlers: WeakMap<ComponentNode, ((error: Error) => void)[]> = new WeakMap();
@@ -38,13 +38,23 @@ function _handleError(node: ComponentNode | null, error: Error, isFirstRound = f
}
}
export function handleError(node: ComponentNode, error: Error) {
const fiber = node.fiber!;
export function handleError(entity: ComponentNode | Fiber, error: Error) {
let node: ComponentNode;
let fiber: Fiber;
if (entity instanceof Fiber) {
fiber = entity;
node = entity.node;
} else {
node = entity;
fiber = entity.fiber!;
}
fibersInError.set(fiber.root, error);
if (!_handleError(node, error, true)) {
const handled = _handleError(node, error, true);
if (!handled) {
try {
node.app.destroy();
} catch (e) {}
}
return handled;
}
+70 -57
View File
@@ -2,7 +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 { fibersInError, handleError } from "./error_handling";
export function makeChildFiber(node: ComponentNode, parent: Fiber): Fiber {
let current = node.fiber;
@@ -106,56 +106,63 @@ export class RootFiber extends Fiber {
complete() {
const node = this.node;
// Step 1: calling all willPatch lifecycle hooks
for (let fiber of this.willPatch) {
// because of the asynchronous nature of the rendering, some parts of the
// UI may have been rendered, then deleted in a followup rendering, and we
// do not want to call onWillPatch in that case.
let node = fiber.node;
if (node.fiber === fiber) {
const component = node.component;
for (let cb of node.willPatch) {
cb.call(component);
let current: Fiber | undefined = undefined;
try {
// Step 1: calling all willPatch lifecycle hooks
for (current of this.willPatch) {
// because of the asynchronous nature of the rendering, some parts of the
// UI may have been rendered, then deleted in a followup rendering, and we
// do not want to call onWillPatch in that case.
let node = current.node;
if (node.fiber === current) {
const component = node.component;
for (let cb of node.willPatch) {
cb.call(component);
}
}
}
}
current = undefined;
// Step 2: patching the dom
node.bdom!.patch(this.bdom!, Object.keys(node.children).length > 0);
this.appliedToDom = true;
// Step 2: patching the dom
node.bdom!.patch(this.bdom!, Object.keys(node.children).length > 0);
this.appliedToDom = true;
// Step 3: calling all destroyed hooks
for (let node of __internal__destroyed) {
for (let cb of node.destroyed) {
cb();
}
}
__internal__destroyed.length = 0;
// Step 4: calling all mounted lifecycle hooks
let current;
let mountedFibers = this.mounted;
while ((current = mountedFibers.pop())) {
if (current.appliedToDom) {
for (let cb of current.node.mounted) {
// Step 3: calling all destroyed hooks
for (let node of __internal__destroyed) {
for (let cb of node.destroyed) {
cb();
}
}
}
__internal__destroyed.length = 0;
// Step 5: calling all patched hooks
let patchedFibers = this.patched;
while ((current = patchedFibers.pop())) {
if (current.appliedToDom) {
for (let cb of current.node.patched) {
cb();
// Step 4: calling all mounted lifecycle hooks
let mountedFibers = this.mounted;
while ((current = mountedFibers.pop())) {
current = current;
if (current.appliedToDom) {
for (let cb of current.node.mounted) {
cb();
}
}
}
}
// unregistering the fiber
node.fiber = null;
// Step 5: calling all patched hooks
let patchedFibers = this.patched;
while ((current = patchedFibers.pop())) {
current = current;
if (current.appliedToDom) {
for (let cb of current.node.patched) {
cb();
}
}
}
// unregistering the fiber
node.fiber = null;
} catch (e) {
if (!handleError(current || this, e)) {
this.reject(e);
}
}
}
}
@@ -177,25 +184,31 @@ export class MountFiber extends RootFiber {
this.position = options.position || "last-child";
}
complete() {
const node = this.node;
node.bdom = this.bdom;
if (this.position === "last-child" || this.target.childNodes.length === 0) {
mount(node.bdom!, this.target);
} else {
const firstChild = this.target.childNodes[0];
mount(node.bdom!, this.target, firstChild);
}
node.status = STATUS.MOUNTED;
this.appliedToDom = true;
let current;
let mountedFibers = this.mounted;
while ((current = mountedFibers.pop())) {
if (current.appliedToDom) {
for (let cb of current.node.mounted) {
cb();
let current: Fiber | undefined = this;
try {
const node = this.node;
node.bdom = this.bdom;
if (this.position === "last-child" || this.target.childNodes.length === 0) {
mount(node.bdom!, this.target);
} else {
const firstChild = this.target.childNodes[0];
mount(node.bdom!, this.target, firstChild);
}
node.status = STATUS.MOUNTED;
this.appliedToDom = true;
let mountedFibers = this.mounted;
while ((current = mountedFibers.pop())) {
if (current.appliedToDom) {
for (let cb of current.node.mounted) {
cb();
}
}
}
node.fiber = null;
} catch (e) {
if (!handleError(current as Fiber, e)) {
this.reject(e);
}
}
node.fiber = null;
}
}
+3 -8
View File
@@ -1,5 +1,5 @@
import { Fiber, RootFiber } from "./fibers";
import { handleError, fibersInError } from "./error_handling";
import { fibersInError } from "./error_handling";
import { STATUS } from "./status";
// -----------------------------------------------------------------------------
@@ -60,13 +60,8 @@ export class Scheduler {
if (fiber.counter === 0) {
if (!hasError) {
try {
fiber.complete();
fiber.resolve();
} catch (e) {
handleError(fiber.node, e);
fiber.reject(e);
}
fiber.complete();
fiber.resolve();
}
this.tasks.delete(fiber);
}