Contribute to this guide

指南监控

监控工具可以保护您免受编辑器崩溃导致的数据丢失。它会在崩溃之前保存您的内容,并创建一个包含完整内容的新编辑器实例。

# 附加功能信息

每个非平凡的软件都有 bug。尽管我们拥有 100% 代码覆盖率、回归测试和每次发布前的测试等高标准,但 CKEditor 5 并非没有 bug。用户使用的浏览器、集成 CKEditor 5 的应用程序,以及您使用的任何第三方插件也都不是完美的。

为了最大限度地减少编辑器崩溃对用户体验的影响,您可以自动重启 WYSIWYG 编辑器,并恢复崩溃前的已保存内容。

监控工具可以实现这一功能。它确保编辑器实例始终运行,即使发生潜在的崩溃。它的工作原理是检测到编辑器崩溃,销毁它,然后自动创建一个新实例,并加载之前编辑器的内容。

请注意,CKEditor 5 API 中最“危险”的部分,例如 editor.model.change()editor.editing.view.change() 或发射器,都包含检查和 try-catch 块,可以检测到未知错误,并在发生错误时重新启动编辑器。

有两种可用的监控工具类型

# 用法

注意:监控工具只能与 从源代码构建的编辑器 一起使用。

# 编辑器监控

安装编辑器 后,将您的 ClassicEditor.create() 调用更改为 watchdog.create(),如下所示


import { ClassicEditor, Bold, EditorWatchdog, Essentials, Italic, Paragraph } from 'ckeditor5';

// Create a watchdog for the given editor type.
const watchdog = new EditorWatchdog( ClassicEditor );

// Create a new editor instance.
watchdog.create( document.querySelector( '#editor' ), {
    plugins: [ Essentials, Paragraph, Bold, Italic ],
    toolbar: [ 'bold', 'italic', 'alignment' ]
} );

换句话说,您的目标是创建一个监控工具实例,并让监控工具创建您要使用的编辑器实例。监控工具将创建一个新的编辑器,如果它崩溃,则通过创建一个新编辑器重新启动它。

每次监控工具检测到崩溃时,都会创建一个新的编辑器实例。因此,编辑器实例不应保存在您的应用程序状态中。请改用 EditorWatchdog#editor 属性。

这也意味着,任何应为任何新编辑器实例执行的代码,都应该作为编辑器插件加载,或者在使用 EditorWatchdog#setCreator()EditorWatchdog#setDestructor() 定义的回调中执行。有关控制编辑器创建和销毁的更多信息,请参阅下一部分。

# 控制编辑器创建和销毁

为了更好地控制编辑器实例的创建和销毁,您可以使用 EditorWatchdog#setCreator(),以及在需要时使用 EditorWatchdog#setDestructor() 方法

// Create an editor watchdog.
const watchdog = new EditorWatchdog();

// Define a callback that will create an editor instance and return it.
watchdog.setCreator( ( elementOrData, editorConfig ) => {
    return ClassicEditor
        .create( elementOrData, editorConfig )
        .then( editor => {
            // Do something with the new editor instance.
            // ...
        } );
} );

// Do something before the editor is destroyed. Return a promise.
watchdog.setDestructor( editor => {
    // Do something before the editor is destroyed.
    // ...
    return editor
        .destroy()
        .then( () => {
            // Do something after the editor is destroyed.
            // ...
        } );
} );

// Create an editor instance and start watching it.
watchdog.create( elementOrData, editorConfig );

默认情况下(未被 setDestructor() 覆盖),编辑器析构函数只是执行 Editor#destroy()

# 编辑器监控 API

其他有用的 方法、属性和事件

watchdog.on( 'error', () => { console.log( 'Editor crashed.' ) } );
watchdog.on( 'restart', () => { console.log( 'Editor was restarted.' ) } );

// Destroy the watchdog and the current editor instance.
watchdog.destroy();

// The current editor instance.
watchdog.editor;

// The current state of the editor.
// The editor might be in one of the following states:
//
// * `initializing` - Before the first initialization, and after crashes, before the editor is ready.
// * `ready` - A state when the user can interact with the editor.
// * `crashed` - A state when an error occurs. It quickly changes to `initializing` or `crashedPermanently` depending on how many and how frequent errors have been caught recently.
// * `crashedPermanently` - A state when the watchdog stops reacting to errors and keeps the editor crashed.
// * `destroyed` - A state when the editor is manually destroyed by the user after calling `watchdog.destroy()`.
watchdog.state;

// Listen to state changes.

let prevState = watchdog.state;

watchdog.on( 'stateChange', () => {
    const currentState = watchdog.state;

    console.log( `State changed from ${ currentState } to ${ prevState }` );

    if ( currentState === 'crashedPermanently' ) {
        watchdog.editor.enableReadOnlyMode( 'crashed-editor' );
    }

    prevState = currentState;
} );

// An array of editor crash information.
watchdog.crashes.forEach( crashInfo => console.log( crashInfo ) );

# 上下文监控

安装编辑器 后,将该功能添加到您的插件列表和工具栏配置中

import { ClassicEditor, ContextWatchdog, Bold, Italic, Context, Essentials, Paragraph } from 'ckeditor5';

// Create a context watchdog and pass the context class with optional watchdog configuration:
const watchdog = new ContextWatchdog( Context, {
    crashNumberLimit: 10
} );

// Initialize the watchdog with the context configuration:
await watchdog.create( {
    plugins: [
        // A list of plugins for the context.
        // ...
    ],
    // More configuration options for the plugin.
    // ...
} );

// Add editor instances.
// You may also use multiple `ContextWatchdog#add()` calls, each adding a single editor.
await watchdog.add( [ {
    id: 'editor1',
    type: 'editor',
    sourceElementOrData: document.querySelector( '#editor' ),
    config: {
        plugins: [ Essentials, Paragraph, Bold, Italic ],
        toolbar: [ 'bold', 'italic', 'alignment' ]
    },
    creator: ( element, config ) => ClassicEditor.create( element, config )
}, {
    id: 'editor2',
    type: 'editor',
    sourceElementOrData: document.querySelector( '#editor' ),
    config: {
        plugins: [ Essentials, Paragraph, Bold, Italic ],
        toolbar: [ 'bold', 'italic', 'alignment' ]
    },
    creator: ( element, config ) => ClassicEditor.create( element, config )
} ] );

// Or:
await watchdog.add( {
    id: 'editor1',
    type: 'editor',
    sourceElementOrData: document.querySelector( '#editor' ),
    config: {
        plugins: [ Essentials, Paragraph, Bold, Italic ],
        toolbar: [ 'bold', 'italic', 'alignment' ]
    },
    creator: ( element, config ) => ClassicEditor.create( element, config )
} );

await watchdog.add( {
    id: 'editor2',
    type: 'editor',
    sourceElementOrData: document.querySelector( '#editor' ),
    config: {
        plugins: [ Essentials, Paragraph, Bold, Italic ],
        toolbar: [ 'bold', 'italic', 'alignment' ]
    },
    creator: ( element, config ) => ClassicEditor.create( element, config )
} );

要销毁某个项实例,请使用 ContextWatchdog#remove

await watchdog.remove( [ 'editor1', 'editor2' ] );

// Or:
await watchdog.remove( 'editor1' );
await watchdog.remove( 'editor2' );

# 上下文监控 API

上下文监控功能提供以下 API

// Creating a watchdog that will use the context class and the watchdog configuration.
const watchdog = new ContextWatchdog( Context, watchdogConfig );

// Setting a custom creator for the context.
watchdog.setCreator( async config => {
    const context = await Context.create( config );

    // Do something when the context is initialized.
    // ...

    return context;
} );

// Setting a custom destructor for the context.
watchdog.setDestructor( async context => {

    // Do something before destroy.
    // ...

    await context.destroy();
} );

// Initializing the context watchdog with the context configuration.
await watchdog.create( contextConfig );

// Adding item configuration (or an array of item configurations).
await watchdog.add( {
    id: 'editor1',
    type: 'editor',
    sourceElementOrData: domElementOrEditorData
    config: editorConfig,
    creator: createEditor,
    destructor: destroyEditor,
} );

await watchdog.add( [
    {
        id: 'editor1',
        type: 'editor',
        sourceElementOrData: domElementOrEditorData
        config: editorConfig,
        creator: createEditor,
        destructor: destroyEditor,
    },
    // More configuration items.
    // ...
] );

// Remove and destroy a given item (or items).
await watchdog.remove( 'editor1' );

await watchdog.remove( [ 'editor1', 'editor2', ... ] );

// Getting the given item instance.
const editor1 = watchdog.getItem( 'editor1' );

// Getting the state of the given item.
const editor1State = watchdog.getItemState( 'editor1' );

// Getting the context state.
const contextState = watchdog.state;

// The `error` event is fired when the context watchdog catches a context-related error.
// Note that errors fired by items are not delegated to `ContextWatchdog#event:error`.
// See also `ContextWatchdog#event:itemError`.
watchdog.on( 'error', ( _, { error } ) => {

// The `restart` event is fired when the context is set back to the `ready` state (after it was in the `crashed` state).
// Similarly, this event is not thrown for internal item restarts.
watchdog.on( 'restart', () => {
    console.log( 'The context has been restarted.' );
} );

// The `itemError` event is fired when an error occurred in one of the added items.
watchdog.on( 'itemError', ( _, { error, itemId } ) => {
    console.log( `An error occurred in an item with the '${ itemId }' ID.` );
} );

// The `itemRestart` event is fired when an item is set back to the `ready` state (after it was in the `crashed` state).
watchdog.on( 'itemRestart', ( _, { itemId } ) => {
    console.log( 'An item with with the '${ itemId }' ID has been restarted.' );
} );

# 配置

EditorWatchdogContextWatchdog 的构造函数都接受一个 配置对象 作为第二个参数,该对象包含以下可选属性。

  • crashNumberLimit – 一个阈值,指定错误数量(默认值为 3)。当达到此限制并且上次错误之间的时间间隔小于 minimumNonErrorTimePeriod 时,看门狗会将其状态更改为 crashedPermanently 并停止重启编辑器。这可以防止无限重启循环。
  • minimumNonErrorTimePeriod – 上次编辑器错误之间的平均毫秒数(默认值为 5000)。当错误之间的时间间隔小于此值且 crashNumberLimit 也达到时,看门狗会将其状态更改为 crashedPermanently 并停止重启编辑器。这可以防止无限重启循环。
  • saveInterval – 在内部保存编辑器数据之间的最小毫秒数(默认值为 5000)。请注意,对于大型文档,这可能会影响编辑器的性能。
const editorWatchdog = new EditorWatchdog( ClassicEditor, {
    minimumNonErrorTimePeriod: 2000,
    crashNumberLimit: 4,
    saveInterval: 1000
} );

请注意,上下文看门狗会将其配置传递给它为添加的编辑器创建的编辑器看门狗。

# 限制

看门狗不会处理在编辑器或上下文初始化(例如,在 Editor.create() 中)和编辑器销毁(例如,在 Editor#destroy() 中)期间抛出的错误。在这些阶段抛出的错误意味着编辑器与应用程序集成的代码中存在问题,这种问题无法通过重启编辑器来解决。