vue3`callWithErrorHandling` 函数

623 阅读10分钟

callWithErrorHandling 是 Vue 3 源码中的一个实用函数,用于在调用用户提供的回调函数时捕获和处理可能发生的错误。这对于确保框架在处理用户代码时的健壮性和稳定性至关重要。

callWithErrorHandling 函数的源码

以下是 callWithErrorHandling 函数的源码:

import { handleError, ErrorTypes } from './errorHandling';
import { isFunction } from '@vue/shared';

export function callWithErrorHandling(
  fn,
  instance,
  type,
  args
) {
  let res;
  try {
    res = args ? fn(...args) : fn();
  } catch (err) {
    handleError(err, instance, type);
  }
  return res;
}

详细解析

  1. 参数解析:

    • fn: 要调用的函数。
    • instance: 当前的组件实例。
    • type: 错误类型,用于标识错误发生的上下文。
    • args: 调用函数时传递的参数。
  2. 调用函数并捕获错误:

    • 使用 try...catch 语句来捕获 fn 执行过程中可能抛出的错误。
    • 如果 args 存在,则使用扩展运算符 ... 将参数传递给 fn,否则直接调用 fn
    • 如果调用过程中发生错误,则捕获错误并调用 handleError 函数进行处理。
  3. 返回结果:

    • 如果函数执行成功,则返回结果 res

handleError 函数

handleError 函数用于处理捕获到的错误。它的实现如下:

import { ErrorTypes } from './errorTypes';
import { callWithAsyncErrorHandling } from './errorHandling';
import { isFunction, isPromise } from '@vue/shared';

export function handleError(
  err,
  instance,
  type
) {
  const contextVNode = instance ? instance.vnode : null;
  if (instance) {
    let cur = instance.parent;
    // Traverse the parent chain to find the errorCaptured hooks
    while (cur) {
      const errorCapturedHooks = cur.ec;
      if (errorCapturedHooks) {
        for (let i = 0; i < errorCapturedHooks.length; i++) {
          if (errorCapturedHooks[i](err, instance, type) === false) {
            return;
          }
        }
      }
      cur = cur.parent;
    }
    // If no errorCaptured hooks, call global error handler
    const appErrorHandler = instance.appContext.config.errorHandler;
    if (appErrorHandler) {
      callWithAsyncErrorHandling(appErrorHandler, null, ErrorTypes.APP_ERROR_HANDLER, [err, instance, type]);
      return;
    }
  }
  logError(err, type, contextVNode);
}

function logError(err, type, contextVNode) {
  if (__DEV__) {
    const info = ErrorTypes[type];
    console.error(`Unhandled error${info ? ` during execution of ${info}` : ``}`);
  }
  console.error(err);
}

关键点

  1. 错误捕获:

    • callWithErrorHandling 使用 try...catch 捕获错误,确保即使用户代码抛出错误,Vue 也能优雅地处理。
  2. 错误处理:

    • handleError 函数会遍历组件实例的父链,查找 errorCaptured 钩子函数,并尝试调用这些钩子。
    • 如果没有找到 errorCaptured 钩子,或者所有钩子都返回 true,则调用全局错误处理器 appErrorHandler
    • 如果仍未处理错误,则在开发环境下输出错误日志。
  3. 异步错误处理:

    • callWithAsyncErrorHandling 用于处理异步错误,确保在异步操作中同样能够捕获和处理错误。

示例

以下是一个使用 callWithErrorHandling 函数的示例:

import { callWithErrorHandling } from 'vue';

// 一个可能抛出错误的函数
function riskyFunction() {
  throw new Error('Something went wrong!');
}

// 调用 riskyFunction 并捕获错误
const result = callWithErrorHandling(riskyFunction, null, 'example');

console.log(result); // undefined,因为函数抛出了错误

callWithErrorHandling 函数在 Vue 3 中用于安全地调用用户提供的回调函数,并捕获和处理可能发生的错误。通过这种方式,Vue 能够确保框架的稳定性,并提供一致的错误处理机制。理解这一机制有助于我们编写更健壮的 Vue 应用,并在调试时更容易定位和解决问题。

vue3是通过什么捕获所有代码块的错误的

在 Vue 3 中,错误处理机制是通过一系列的工具函数和策略来捕获和处理错误的,而不仅仅是简单地为每个代码块都加上 try...catch。Vue 3 尝试在不同的上下文中捕获错误,包括组件生命周期钩子、事件处理函数、渲染函数等。下面我们详细讲解 Vue 3 如何捕获和处理这些错误。

全局错误处理

Vue 3 提供了一个全局的错误处理机制,通过 config.errorHandler 来捕获未处理的错误。开发者可以通过配置全局错误处理器来处理应用中的所有错误。

const app = Vue.createApp({});

app.config.errorHandler = (err, instance, info) => {
  console.error(`Error: ${err.toString()}\nInfo: ${info}`);
};

错误处理工具函数

Vue 3 使用了一些工具函数来捕获和处理错误,主要包括 callWithErrorHandlingcallWithAsyncErrorHandling

callWithErrorHandling

前面已经详细介绍过,这个函数用于同步调用,并捕获和处理错误。

callWithAsyncErrorHandling

callWithAsyncErrorHandling 用于处理异步函数中的错误。

import { handleError, ErrorTypes } from './errorHandling';

export function callWithAsyncErrorHandling(
  fn,
  instance,
  type,
  args
) {
  if (isFunction(fn)) {
    const res = fn(...args);
    if (res && isPromise(res)) {
      res.catch(err => {
        handleError(err, instance, type);
      });
    }
    return res;
  }
  const values = [];
  for (let i = 0; i < fn.length; i++) {
    values.push(callWithAsyncErrorHandling(fn[i], instance, type, args));
  }
  return values;
}

错误捕获的应用场景

Vue 3 在多个场景下使用这些工具函数来捕获错误:

  1. 生命周期钩子:

    • 在组件的生命周期钩子中,Vue 3 使用 invokeArrayFns 函数来调用钩子,并通过 callWithErrorHandling 捕获错误。
    function invokeArrayFns(fns, arg) {
      for (let i = 0; i < fns.length; i++) {
        callWithErrorHandling(fns[i], null, null, [arg]);
      }
    }
    
  2. 渲染函数:

    • 渲染函数是 Vue 组件的核心部分,Vue 3 使用 renderComponentRoot 函数来执行渲染,并通过 callWithErrorHandling 捕获错误。
    function renderComponentRoot(instance) {
      const { type: Component, vnode, proxy, withProxy, props, slots, attrs, emit, render, renderCache, data, setupState, ctx } = instance;
      let result;
      try {
        result = render.call(proxy, proxy);
      } catch (err) {
        handleError(err, instance, ErrorTypes.RENDER_FUNCTION);
      }
      return result;
    }
    
  3. 事件处理函数:

    • 对于事件处理函数,Vue 3 使用 callWithErrorHandling 来捕获和处理错误。
    function callWithAsyncErrorHandling(
      fn,
      instance,
      type,
      args
    ) {
      if (isFunction(fn)) {
        const res = fn(...args);
        if (res && isPromise(res)) {
          res.catch(err => {
            handleError(err, instance, type);
          });
        }
        return res;
      }
      const values = [];
      for (let i = 0; i < fn.length; i++) {
        values.push(callWithAsyncErrorHandling(fn[i], instance, type, args));
      }
      return values;
    }
    

错误处理示例

以下是一个示例,展示了如何在 Vue 组件中捕获和处理错误:

const app = Vue.createApp({
  data() {
    return {
      message: 'Hello Vue!'
    };
  },
  created() {
    // 这里的错误会被 callWithErrorHandling 捕获
    this.throwError();
  },
  methods: {
    throwError() {
      throw new Error('An error occurred!');
    }
  }
});

app.config.errorHandler = (err, instance, info) => {
  console.error(`Error: ${err.toString()}\nInfo: ${info}`);
};

app.mount('#app');

在这个示例中,throwError 方法会在 created 钩子中被调用,并抛出一个错误。这个错误会被 callWithErrorHandling 捕获,并传递给全局的 errorHandler

Vue 3 通过一系列的工具函数和策略来捕获和处理错误,而不仅仅是简单地为每个代码块都加上 try...catch。这些工具函数包括 callWithErrorHandlingcallWithAsyncErrorHandling,它们被应用在组件生命周期钩子、渲染函数、事件处理函数等多个场景中。这种机制确保了 Vue 应用的健壮性和稳定性,使得开发者可以更容易地调试和处理错误。

在 Vue 3 中,生命周期钩子(如 created)的执行是通过一系列内部机制来管理的。这些钩子的调用会被包装在错误处理函数中,以确保任何在钩子执行过程中抛出的错误都能被捕获和处理。

生命周期钩子捕获详解

下面是详细的代码讲解,展示了 created 钩子是如何被 callWithErrorHandling 捕获的。

1. 组件实例化和生命周期钩子注册

当一个组件实例化时,Vue 会创建一个组件实例对象,并注册它的生命周期钩子。以下是一个简化的组件实例化过程:

function createComponentInstance(vnode, parent) {
  const instance = {
    vnode,
    type: vnode.type,
    parent,
    appContext: parent ? parent.appContext : vnode.appContext,
    // 其他属性省略...
    isMounted: false,
    // 存储生命周期钩子的数组
    bc: null, // beforeCreate
    c: null, // created
    bm: null, // beforeMount
    m: null, // mounted
    bu: null, // beforeUpdate
    u: null, // updated
    // 其他生命周期钩子省略...
  };
  return instance;
}

2. 注册生命周期钩子

在组件的 setup 过程中,Vue 会将生命周期钩子注册到组件实例上。例如:

function setupComponent(instance) {
  const Component = instance.type;

  // 调用 setup 函数
  const setupResult = Component.setup(instance.props, {
    attrs: instance.attrs,
    slots: instance.slots,
    emit: instance.emit
  });

  // 处理 setupResult 省略...

  // 注册生命周期钩子
  registerLifecycleHooks(instance, Component);

  // 其他 setup 逻辑省略...
}

function registerLifecycleHooks(instance, Component) {
  const { created } = Component;
  if (created) {
    instance.c = created;
  }
}

3. 调用生命周期钩子

在组件的 setup 完成后,Vue 会调用注册的生命周期钩子。调用钩子的过程会使用 callWithErrorHandling 来捕获和处理错误。

function invokeLifecycleHook(hook, instance) {
  if (hook) {
    callWithErrorHandling(hook, instance, ErrorTypes.LIFECYCLE_HOOK);
  }
}

function setupComponent(instance) {
  // 其他 setup 逻辑省略...

  // 调用 created 钩子
  invokeLifecycleHook(instance.c, instance);

  // 其他 setup 逻辑省略...
}

4. callWithErrorHandling 函数

callWithErrorHandling 函数用于捕获和处理错误。它会尝试调用传入的函数,并在发生错误时捕获它。

import { handleError, ErrorTypes } from './errorHandling';

export function callWithErrorHandling(fn, instance, type, args) {
  let res;
  try {
    res = args ? fn(...args) : fn();
  } catch (err) {
    handleError(err, instance, type);
  }
  return res;
}

5. 错误处理函数 handleError

handleError 函数会将捕获的错误传递给全局的错误处理器。

export function handleError(err, instance, type) {
  const contextVNode = instance ? instance.vnode : null;
  if (instance) {
    let cur = instance.parent;
    // 向上冒泡,查找错误处理器
    while (cur) {
      const errorCapturedHooks = cur.ec;
      if (errorCapturedHooks) {
        for (let i = 0; i < errorCapturedHooks.length; i++) {
          if (errorCapturedHooks[i](err, instance, type)) {
            return;
          }
        }
      }
      cur = cur.parent;
    }
  }
  // 全局错误处理器
  const appErrorHandler = instance.appContext.config.errorHandler;
  if (appErrorHandler) {
    callWithErrorHandling(appErrorHandler, null, ErrorTypes.APP_ERROR_HANDLER, [err, instance, type]);
  } else {
    console.error(err);
  }
}

示例回顾

以下是示例组件回顾,展示了 created 钩子如何被捕获:

const app = Vue.createApp({
  data() {
    return {
      message: 'Hello Vue!'
    };
  },
  created() {
    // 这里的错误会被 callWithErrorHandling 捕获
    this.throwError();
  },
  methods: {
    throwError() {
      throw new Error('An error occurred!');
    }
  }
});

app.config.errorHandler = (err, instance, info) => {
  console.error(`Error: ${err.toString()}\nInfo: ${info}`);
};

app.mount('#app');

在这个示例中,当 created 钩子被调用时,callWithErrorHandling 会捕获 throwError 方法抛出的错误,并将其传递给全局的 errorHandler

渲染函数和事件处理函数类似通过这种机制,Vue 3 能够在多个上下文中捕获和处理错误,确保应用的健壮性和稳定性。

vue3的错误处理相比于vue2好在哪里

Vue 3 相比 Vue 2 在错误处理方面做了许多改进,这些改进使得开发者能够更好地捕获和处理错误,提高应用的健壮性和稳定性。以下是 Vue 3 在错误处理方面的一些主要改进,并附有详细代码讲解。

1. 更加一致和全面的错误处理

Vue 3 提供了一致的错误处理机制,通过 callWithErrorHandlingcallWithAsyncErrorHandling 函数,确保在不同的上下文中(生命周期钩子、渲染函数、事件处理函数等)都能捕获和处理错误。

Vue 2 的错误处理

在 Vue 2 中,错误处理主要依赖于全局的 errorHandler,并且在某些情况下(如异步操作)可能无法捕获所有错误。

Vue.config.errorHandler = function (err, vm, info) {
  console.error(`Error: ${err.toString()}\nInfo: ${info}`);
};

Vue 3 的错误处理

在 Vue 3 中,错误处理函数被封装在 callWithErrorHandlingcallWithAsyncErrorHandling 中,确保在不同的上下文中都能捕获错误。

import { handleError, ErrorTypes } from './errorHandling';

export function callWithErrorHandling(fn, instance, type, args) {
  let res;
  try {
    res = args ? fn(...args) : fn();
  } catch (err) {
    handleError(err, instance, type);
  }
  return res;
}

export function callWithAsyncErrorHandling(fn, instance, type, args) {
  if (isFunction(fn)) {
    const res = callWithErrorHandling(fn, instance, type, args);
    if (res && isPromise(res)) {
      res.catch(err => {
        handleError(err, instance, type);
      });
    }
    return res;
  }

  const values = [];
  for (let i = 0; i < fn.length; i++) {
    values.push(callWithAsyncErrorHandling(fn[i], instance, type, args));
  }
  return values;
}

2. 更加细粒度的错误类型

Vue 3 引入了错误类型(ErrorTypes),使得开发者能够更精确地知道错误发生的上下文,从而更好地处理错误。

export const enum ErrorTypes {
  LIFECYCLE_HOOK = 'lifecycle hook',
  COMPONENT_EVENT_HANDLER = 'component event handler',
  RENDER_FUNCTION = 'render function',
  WATCH_GETTER = 'watch getter',
  WATCH_CALLBACK = 'watch callback',
  WATCH_CLEANUP = 'watch cleanup',
  NATIVE_EVENT_HANDLER = 'native event handler',
  COMPONENT_RENDERER = 'component renderer',
  VNODE_HOOK = 'vnode hook',
  DIRECTIVE_HOOK = 'directive hook',
  TRANSITION_HOOK = 'transition hook',
  APP_ERROR_HANDLER = 'app error handler',
  APP_WARN_HANDLER = 'app warn handler',
  FUNCTION_REF = 'function ref',
  ASYNC_COMPONENT_LOADER = 'async component loader',
  SCHEDULER = 'scheduler'
}

3. 更好的异步错误处理

Vue 3 通过 callWithAsyncErrorHandling 函数改进了异步错误处理,确保异步操作中的错误也能被捕获和处理。

export function callWithAsyncErrorHandling(fn, instance, type, args) {
  if (isFunction(fn)) {
    const res = callWithErrorHandling(fn, instance, type, args);
    if (res && isPromise(res)) {
      res.catch(err => {
        handleError(err, instance, type);
      });
    }
    return res;
  }

  const values = [];
  for (let i = 0; i < fn.length; i++) {
    values.push(callWithAsyncErrorHandling(fn[i], instance, type, args));
  }
  return values;
}

4. 更好的全局错误处理

Vue 3 提供了更灵活的全局错误处理机制,允许开发者在应用级别定义错误处理器。

export function handleError(err, instance, type) {
  const contextVNode = instance ? instance.vnode : null;
  if (instance) {
    let cur = instance.parent;
    // 向上冒泡,查找错误处理器
    while (cur) {
      const errorCapturedHooks = cur.ec;
      if (errorCapturedHooks) {
        for (let i = 0; i < errorCapturedHooks.length; i++) {
          if (errorCapturedHooks[i](err, instance, type)) {
            return;
          }
        }
      }
      cur = cur.parent;
    }
  }
  // 全局错误处理器
  const appErrorHandler = instance.appContext.config.errorHandler;
  if (appErrorHandler) {
    callWithErrorHandling(appErrorHandler, null, ErrorTypes.APP_ERROR_HANDLER, [err, instance, type]);
  } else {
    console.error(err);
  }
}

5. 示例对比

Vue 2 示例

在 Vue 2 中,错误处理主要通过全局错误处理器完成:

new Vue({
  created() {
    this.throwError();
  },
  methods: {
    throwError() {
      throw new Error('An error occurred!');
    }
  },
  errorHandler(err, vm, info) {
    console.error(`Error: ${err.toString()}\nInfo: ${info}`);
  }
}).$mount('#app');

Vue 3 示例

在 Vue 3 中,错误处理更加全面和一致:

const app = Vue.createApp({
  created() {
    this.throwError();
  },
  methods: {
    throwError() {
      throw new Error('An error occurred!');
    }
  }
});

app.config.errorHandler = (err, instance, info) => {
  console.error(`Error: ${err.toString()}\nInfo: ${info}`);
};

app.mount('#app');

总结

Vue 3 在错误处理方面的改进主要体现在以下几个方面:

  1. 一致和全面的错误处理:通过 callWithErrorHandlingcallWithAsyncErrorHandling 确保在不同上下文中都能捕获错误。
  2. 细粒度的错误类型:引入错误类型,使得错误处理更加精确。
  3. 改进的异步错误处理:确保异步操作中的错误也能被捕获和处理。
  4. 灵活的全局错误处理:允许在应用级别定义错误处理器。

这些改进使得 Vue 3 的错误处理机制更加健壮和灵活,有助于开发者更好地管理和处理应用中的错误。