[FIX] component: propagate errors to parent

Before this commit, the error handling process was too naive: once an
error occurs in a rendering, owl catches it, looks for a component that
implements the catchError method, then calls it.

However, in real life, we sometimes need to rethrow that error (or
another one) to propagate the error to some parent handler. This error
needs to be handled by the closest parent component that implements
catchError.

This is what this commit implements: it wraps the catchError call in a
try/catch, then in case of errors, try to handle it by a parent.
This commit is contained in:
Géry Debongnie
2021-06-18 10:21:32 +02:00
committed by aab-odoo
parent d12115554c
commit 922eb7cd98
2 changed files with 90 additions and 10 deletions
+23 -9
View File
@@ -326,16 +326,30 @@ export class Fiber {
const qweb = component.env.qweb; const qweb = component.env.qweb;
let root = component; let root = component;
let canCatch = false;
while (component && !(canCatch = !!component.catchError)) {
root = component;
component = component.__owl__.parent!;
}
qweb.trigger("error", error);
if (canCatch) { function handle(error) {
component.catchError!(error); let canCatch = false;
} else { qweb.trigger("error", error);
while (component && !(canCatch = !!component.catchError)) {
root = component;
component = component.__owl__.parent!;
}
if (canCatch) {
try {
component.catchError!(error);
} catch (e) {
root = component;
component = component.__owl__.parent!;
return handle(e);
}
return true;
}
return false;
}
let isHandled = handle(error);
if (!isHandled) {
// the 3 next lines aim to mark the root fiber as being in error, and // the 3 next lines aim to mark the root fiber as being in error, and
// to force it to end, without waiting for its children // to force it to end, without waiting for its children
this.root.counter = 0; this.root.counter = 0;
+67 -1
View File
@@ -1,4 +1,4 @@
import { Component, Env, STATUS } from "../../src/component/component"; import { Component, Env, mount, STATUS } from "../../src/component/component";
import { useState } from "../../src/hooks"; import { useState } from "../../src/hooks";
import { xml } from "../../src/tags"; import { xml } from "../../src/tags";
import { makeTestEnv, makeTestFixture, nextTick } from "../helpers"; import { makeTestEnv, makeTestFixture, nextTick } from "../helpers";
@@ -520,4 +520,70 @@ describe("component error handling (catchError)", () => {
expect(error).toBeDefined(); expect(error).toBeDefined();
expect(error.message).toBe("Cannot read property 'y' of undefined"); expect(error.message).toBe("Cannot read property 'y' of undefined");
}); });
test("simple catchError", async () => {
class Boom extends Component {
static template = xml`<div t-esc="a.b.c"/>`;
}
class Parent extends Component {
static template = xml`
<div>
<t t-if="error">Error</t>
<t t-else="">
<Boom />
</t>
</div>`;
static components = { Boom };
error = false;
catchError(error) {
this.error = error;
this.render();
}
}
await mount(Parent, { target: fixture });
expect(fixture.innerHTML).toBe("<div>Error</div>");
});
test("catchError in catchError", async () => {
class Boom extends Component {
static template = xml`<div t-esc="a.b.c"/>`;
}
class Child extends Component {
static template = xml`
<div>
<Boom />
</div>`;
static components = { Boom };
catchError(error) {
throw error;
}
}
class Parent extends Component {
static template = xml`
<div>
<t t-if="error">Error</t>
<t t-else="">
<Child />
</t>
</div>`;
static components = { Child };
error = false;
catchError(error) {
this.error = error;
this.render();
}
}
await mount(Parent, { target: fixture });
expect(fixture.innerHTML).toBe("<div>Error</div>");
});
}); });