From 09593c94c3f9c96a07cac21582942595f58a073e Mon Sep 17 00:00:00 2001 From: Evan You Date: Fri, 30 Aug 2019 15:15:23 -0400 Subject: [PATCH] feat: error handling in scheduler --- packages/runtime-core/src/apiWatch.ts | 29 ++++++++-------------- packages/runtime-core/src/component.ts | 10 ++++---- packages/runtime-core/src/errorHandling.ts | 28 ++++++++++----------- packages/runtime-core/src/scheduler.ts | 28 ++++++++++----------- packages/runtime-dom/src/modules/events.ts | 6 ++--- 5 files changed, 45 insertions(+), 56 deletions(-) diff --git a/packages/runtime-core/src/apiWatch.ts b/packages/runtime-core/src/apiWatch.ts index 94f3d69d3..6d092b2a4 100644 --- a/packages/runtime-core/src/apiWatch.ts +++ b/packages/runtime-core/src/apiWatch.ts @@ -10,7 +10,7 @@ import { EMPTY_OBJ, isObject, isArray, isFunction } from '@vue/shared' import { recordEffect } from './apiReactivity' import { getCurrentInstance } from './component' import { - UserExecutionContexts, + ErrorTypes, callWithErrorHandling, callWithAsyncErrorHandling } from './errorHandling' @@ -92,22 +92,14 @@ function doWatch( s => isRef(s) ? s.value - : callWithErrorHandling( - s, - instance, - UserExecutionContexts.WATCH_GETTER - ) + : callWithErrorHandling(s, instance, ErrorTypes.WATCH_GETTER) ) } else if (isRef(source)) { getter = () => source.value } else if (cb) { // getter with cb getter = () => - callWithErrorHandling( - source, - instance, - UserExecutionContexts.WATCH_GETTER - ) + callWithErrorHandling(source, instance, ErrorTypes.WATCH_GETTER) } else { // no cb -> simple effect getter = () => { @@ -117,7 +109,7 @@ function doWatch( return callWithErrorHandling( source, instance, - UserExecutionContexts.WATCH_CALLBACK, + ErrorTypes.WATCH_CALLBACK, [registerCleanup] ) } @@ -132,7 +124,7 @@ function doWatch( const registerCleanup: CleanupRegistrator = (fn: () => void) => { // TODO wrap the cleanup fn for error handling cleanup = runner.onStop = () => { - callWithErrorHandling(fn, instance, UserExecutionContexts.WATCH_CLEANUP) + callWithErrorHandling(fn, instance, ErrorTypes.WATCH_CLEANUP) } } @@ -145,12 +137,11 @@ function doWatch( if (cleanup) { cleanup() } - callWithAsyncErrorHandling( - cb, - instance, - UserExecutionContexts.WATCH_CALLBACK, - [newValue, oldValue, registerCleanup] - ) + callWithAsyncErrorHandling(cb, instance, ErrorTypes.WATCH_CALLBACK, [ + newValue, + oldValue, + registerCleanup + ]) oldValue = newValue } } diff --git a/packages/runtime-core/src/component.ts b/packages/runtime-core/src/component.ts index a7292cf28..82242d695 100644 --- a/packages/runtime-core/src/component.ts +++ b/packages/runtime-core/src/component.ts @@ -8,7 +8,7 @@ import { PatchFlags } from './patchFlags' import { ShapeFlags } from './shapeFlags' import { warn } from './warning' import { - UserExecutionContexts, + ErrorTypes, handleError, callWithErrorHandling, callWithAsyncErrorHandling @@ -238,7 +238,7 @@ export function createComponentInstance( callWithAsyncErrorHandling( handler[i], instance, - UserExecutionContexts.COMPONENT_EVENT_HANDLER, + ErrorTypes.COMPONENT_EVENT_HANDLER, args ) } @@ -246,7 +246,7 @@ export function createComponentInstance( callWithAsyncErrorHandling( handler, instance, - UserExecutionContexts.COMPONENT_EVENT_HANDLER, + ErrorTypes.COMPONENT_EVENT_HANDLER, args ) } @@ -287,7 +287,7 @@ export function setupStatefulComponent(instance: ComponentInstance) { const setupResult = callWithErrorHandling( setup, instance, - UserExecutionContexts.SETUP_FUNCTION, + ErrorTypes.SETUP_FUNCTION, [propsProxy, setupContext] ) currentInstance = null @@ -382,7 +382,7 @@ export function renderComponentRoot(instance: ComponentInstance): VNode { ) } } catch (err) { - handleError(err, instance, UserExecutionContexts.RENDER_FUNCTION) + handleError(err, instance, ErrorTypes.RENDER_FUNCTION) return createVNode(Empty) } } diff --git a/packages/runtime-core/src/errorHandling.ts b/packages/runtime-core/src/errorHandling.ts index cb38ad850..d868c0018 100644 --- a/packages/runtime-core/src/errorHandling.ts +++ b/packages/runtime-core/src/errorHandling.ts @@ -4,7 +4,7 @@ import { warn, pushWarningContext, popWarningContext } from './warning' // contexts where user provided function may be executed, in addition to // lifecycle hooks. -export const enum UserExecutionContexts { +export const enum ErrorTypes { SETUP_FUNCTION = 1, RENDER_FUNCTION, WATCH_GETTER, @@ -29,24 +29,24 @@ export const ErrorTypeStrings: Record = { [LifecycleHooks.ERROR_CAPTURED]: 'errorCaptured hook', [LifecycleHooks.RENDER_TRACKED]: 'renderTracked hook', [LifecycleHooks.RENDER_TRIGGERED]: 'renderTriggered hook', - [UserExecutionContexts.SETUP_FUNCTION]: 'setup function', - [UserExecutionContexts.RENDER_FUNCTION]: 'render function', - [UserExecutionContexts.WATCH_GETTER]: 'watcher getter', - [UserExecutionContexts.WATCH_CALLBACK]: 'watcher callback', - [UserExecutionContexts.WATCH_CLEANUP]: 'watcher cleanup function', - [UserExecutionContexts.NATIVE_EVENT_HANDLER]: 'native event handler', - [UserExecutionContexts.COMPONENT_EVENT_HANDLER]: 'component event handler', - [UserExecutionContexts.SCHEDULER]: + [ErrorTypes.SETUP_FUNCTION]: 'setup function', + [ErrorTypes.RENDER_FUNCTION]: 'render function', + [ErrorTypes.WATCH_GETTER]: 'watcher getter', + [ErrorTypes.WATCH_CALLBACK]: 'watcher callback', + [ErrorTypes.WATCH_CLEANUP]: 'watcher cleanup function', + [ErrorTypes.NATIVE_EVENT_HANDLER]: 'native event handler', + [ErrorTypes.COMPONENT_EVENT_HANDLER]: 'component event handler', + [ErrorTypes.SCHEDULER]: 'scheduler flush. This may be a Vue internals bug. ' + 'Please open an issue at https://new-issue.vuejs.org/?repo=vuejs/vue' } -type ErrorTypes = LifecycleHooks | UserExecutionContexts +type AllErrorTypes = LifecycleHooks | ErrorTypes export function callWithErrorHandling( fn: Function, instance: ComponentInstance | null, - type: ErrorTypes, + type: AllErrorTypes, args?: any[] ) { let res: any @@ -61,7 +61,7 @@ export function callWithErrorHandling( export function callWithAsyncErrorHandling( fn: Function, instance: ComponentInstance | null, - type: ErrorTypes, + type: AllErrorTypes, args?: any[] ) { const res = callWithErrorHandling(fn, instance, type, args) @@ -76,7 +76,7 @@ export function callWithAsyncErrorHandling( export function handleError( err: Error, instance: ComponentInstance | null, - type: ErrorTypes + type: AllErrorTypes ) { const contextVNode = instance ? instance.vnode : null let cur: ComponentInstance | null = instance && instance.parent @@ -100,7 +100,7 @@ export function handleError( logError(err, type, contextVNode) } -function logError(err: Error, type: ErrorTypes, contextVNode: VNode | null) { +function logError(err: Error, type: AllErrorTypes, contextVNode: VNode | null) { if (__DEV__) { const info = ErrorTypeStrings[type] if (contextVNode) { diff --git a/packages/runtime-core/src/scheduler.ts b/packages/runtime-core/src/scheduler.ts index a81ecdfff..6f8ccaa91 100644 --- a/packages/runtime-core/src/scheduler.ts +++ b/packages/runtime-core/src/scheduler.ts @@ -1,3 +1,5 @@ +import { handleError, ErrorTypes } from './errorHandling' + const queue: Function[] = [] const postFlushCbs: Function[] = [] const p = Promise.resolve() @@ -8,31 +10,23 @@ export function nextTick(fn?: () => void): Promise { return fn ? p.then(fn) : p } -type ErrorHandler = (err: Error) => void - -export function queueJob(job: () => void, onError?: ErrorHandler) { +export function queueJob(job: () => void) { if (queue.indexOf(job) === -1) { queue.push(job) - queueFlush(onError) + if (!isFlushing) { + nextTick(flushJobs) + } } } -export function queuePostFlushCb( - cb: Function | Function[], - onError?: ErrorHandler -) { +export function queuePostFlushCb(cb: Function | Function[]) { if (Array.isArray(cb)) { postFlushCbs.push.apply(postFlushCbs, cb) } else { postFlushCbs.push(cb) } - queueFlush(onError) -} - -function queueFlush(onError?: ErrorHandler) { if (!isFlushing) { - const p = nextTick(flushJobs) - if (onError) p.catch(onError) + nextTick(flushJobs) } } @@ -75,7 +69,11 @@ function flushJobs(seenJobs?: JobCountMap) { } } } - job() + try { + job() + } catch (err) { + handleError(err, null, ErrorTypes.SCHEDULER) + } } flushPostFlushCbs() isFlushing = false diff --git a/packages/runtime-dom/src/modules/events.ts b/packages/runtime-dom/src/modules/events.ts index 8793ad948..d6663fd53 100644 --- a/packages/runtime-dom/src/modules/events.ts +++ b/packages/runtime-dom/src/modules/events.ts @@ -3,7 +3,7 @@ import { ComponentInstance, callWithAsyncErrorHandling } from '@vue/runtime-core' -import { UserExecutionContexts } from 'packages/runtime-core/src/errorHandling' +import { ErrorTypes } from 'packages/runtime-core/src/errorHandling' interface Invoker extends Function { value: EventValue @@ -77,7 +77,7 @@ function createInvoker(value: any, instance: ComponentInstance | null) { callWithAsyncErrorHandling( value[i], instance, - UserExecutionContexts.NATIVE_EVENT_HANDLER, + ErrorTypes.NATIVE_EVENT_HANDLER, args ) } @@ -85,7 +85,7 @@ function createInvoker(value: any, instance: ComponentInstance | null) { callWithAsyncErrorHandling( value, instance, - UserExecutionContexts.NATIVE_EVENT_HANDLER, + ErrorTypes.NATIVE_EVENT_HANDLER, args ) }