Vue but underthehood: Part 3 - The Safety Valve
Introduction
In previous parts, we explored The Proxy Foundation and The Language Engine. Now we'll examine The Safety Valve - Vue's sophisticated error handling system that keeps your application running even when things go wrong.
The Error Handling Pipeline
Vue 3 implements a comprehensive error handling pipeline that captures errors at multiple levels:
// Internal error handling flow
export function handleError(
err: unknown,
instance: ComponentInternalInstance | null,
type: ErrorTypes
) {
const contextVNode = instance ? instance.vnode : null;
if (instance) {
let cur = instance.parent;
const exposedInstance = instance.proxy;
const errorInfo = ErrorTypeStrings[type];
// Traverse up the component tree
while (cur) {
const errorCapturedHooks = cur.ec;
if (errorCapturedHooks) {
for (let i = 0; i < errorCapturedHooks.length; i++) {
if (
errorCapturedHooks[i](err, exposedInstance, errorInfo) === false
) {
return;
}
}
}
cur = cur.parent;
}
}
// If not handled, log to console
logError(err, type, contextVNode);
}
Error Types
Vue categorizes errors into different types for better debugging:
export enum ErrorCodes {
SETUP_FUNCTION,
RENDER_FUNCTION,
WATCH_CALLBACK,
WATCH_CLEANUP,
WATCH_GETTER,
COMPONENT_EVENT_HANDLER,
VNODE_HOOK,
DIRECTIVE_HOOK,
TRANSITION_HOOK,
APP_ERROR_HANDLER,
APP_WARN_HANDLER,
FUNCTION_REF,
ASYNC_COMPONENT_LOADER,
SCHEDULER,
}
const ErrorTypeStrings: Record<number, string> = {
[ErrorCodes.SETUP_FUNCTION]: "setup function",
[ErrorCodes.RENDER_FUNCTION]: "render function",
[ErrorCodes.WATCH_CALLBACK]: "watcher callback",
// ... more mappings
};
The onErrorCaptured Hook
Components can capture errors from child components using onErrorCaptured:
<script setup lang="ts">
import { onErrorCaptured, ref } from 'vue';
const error = ref<Error | null>(null);
onErrorCaptured((err, instance, info) => {
console.error('Error captured:', err);
console.log('Component:', instance);
console.log('Error info:', info);
// Store error for display
error.value = err as Error;
// Return false to prevent propagation
return false;
});
</script>
<template>
<div>
<ErrorDisplay v-if="error" :error="error" />
<slot v-else />
</div>
</template>
Global Error Handler
You can set up a global error handler to catch all unhandled errors:
import { createApp } from "vue";
import App from "./App.vue";
const app = createApp(App);
app.config.errorHandler = (err, instance, info) => {
// Send to error tracking service
console.error("Global error:", err);
console.log("Vue instance:", instance);
console.log("Error info:", info);
// Report to monitoring service
reportError({
error: err,
component: instance?.$options.name,
info,
});
};
app.mount("#app");
Error Boundaries Pattern
Create reusable error boundary components:
<script setup lang="ts">
import { ref, onErrorCaptured, provide } from 'vue';
const error = ref<Error | null>(null);
const errorInfo = ref<string>('');
onErrorCaptured((err, instance, info) => {
error.value = err as Error;
errorInfo.value = info;
return false; // Stop propagation
});
const reset = () => {
error.value = null;
errorInfo.value = '';
};
provide('resetError', reset);
</script>
<template>
<div class="error-boundary">
<div v-if="error" class="error-state">
<h2>Something went wrong</h2>
<pre></pre>
<p>Error occurred in: </p>
<button @click="reset">Try Again</button>
</div>
<slot v-else />
</div>
</template>
Handling Async Errors
Vue also handles errors in async operations:
import { onMounted, ref } from "vue";
const data = ref(null);
onMounted(async () => {
try {
// Vue will catch errors thrown here
const response = await fetch("/api/data");
data.value = await response.json();
} catch (err) {
// Manual error handling
console.error("Failed to fetch data:", err);
}
});
Scheduler Queue Errors
Vue's scheduler also has error handling for batch updates:
function flushJobs() {
try {
for (let i = 0; i < queue.length; i++) {
const job = queue[i];
if (job && job.active !== false) {
callWithErrorHandling(job, null, ErrorCodes.SCHEDULER);
}
}
} finally {
// Cleanup even if errors occurred
queue.length = 0;
isFlushing = false;
}
}
Key Takeaways
- Vue provides a multi-layered error handling system
- Errors bubble up the component tree until caught
onErrorCapturedallows components to handle child errors- Global error handlers provide application-wide error management
- Error boundaries are a powerful pattern for fault tolerance
- Vue categorizes errors by type for better debugging
What's Next?
In Part 4: The Identity Crisis, we'll explore Vue's key system and how it manages component and element identity for efficient updates.
This is Part 3 of the "Vue but underthehood" series.
