mirror of
https://github.com/odoo/owl.git
synced 2025-10-06 19:59:41 +07:00
[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:
+23
-9
@@ -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;
|
||||||
|
|||||||
@@ -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>");
|
||||||
|
});
|
||||||
});
|
});
|
||||||
|
|||||||
Reference in New Issue
Block a user