错误机制

# 源码解析

文件路径vue/src/core/util/error.js




































































 



























 

 

















 





















 






 



















import config from '../config'
import {warn} from './debug'
import {inBrowser, inWeex} from './env'  // 运行的平台
import {isPromise} from 'shared/util'

/*自己实现一个版本,前几天一个npm小项目的更新给整个npm生态系统制造了一场混乱,影响到了数百万 JS 项目。这个库就是 is-promise;
* function isPromise(val){
*    return (typeof val === 'object' || typeof val === 'function') && val !==null &&  typeof val.then === 'function' && typeof val.catch === 'function'
*  }
* */
import {pushTarget, popTarget} from '../observer/dep'

export function handleError(err: Error, vm: any, info: string) {
  // 处理错误信息, 进行错误上报
  // err错误对象
  // vm Vue实例
  // info是 Vue 特定的错误信息,比如错误所在的生命周期钩子
  // 只在 2.2.0+ 可用

  // 当错误函数处理错误时,停用deps跟踪以避免可能出现的infinite rendering
  // See: https://github.com/vuejs/vuex/issues/1505
  pushTarget()
  try {
    if (vm) {
      let cur = vm  // 获取当前的错误组件,然后递归查找当前组件的父组件,依次调用errorCaptured 方法。
      while ((cur = cur.$parent)) {
        const hooks = cur.$options.errorCaptured
        if (hooks) {
          for (let i = 0; i < hooks.length; i++) {
            try {
              // 逐个执行
              const capture = hooks[i].call(cur, err, vm, info) === false
              if (capture) return   // 返回false 默认不会向上递归
            } catch (e) {
              globalHandleError(e, cur, 'errorCaptured hook')
            }
          }
        }
      }
    }
    // 最后执行全局的errorHandler,返回返回false就中断了
    globalHandleError(err, vm, info)
  } finally {
    popTarget()
  }
}
// 处理异步错误
export function invokeWithErrorHandling(
  handler: Function,
  context: any,
  args: null | any[],
  vm: any,
  info: string
) {
  let res
  try {
    res = args ? handler.apply(context, args) : handler.call(context)
    if (res && !res._isVue && isPromise(res) && !res._handled) {
      res.catch(e => handleError(e, vm, info + ` (Promise/async)`))
      res._handled = true
    }
  } catch (e) {
    handleError(e, vm, info)
  }
  return res
}

function globalHandleError(err, vm, info) {
  if (config.errorHandler) {
    try {
      return config.errorHandler.call(null, err, vm, info)
    } catch (e) {
      //如果用户有意在处理程序中抛出原始错误,
      //不要记录两次,一次性输出
      if (e !== err) {
        logError(e, null, 'config.errorHandler')
      }
    }
  }
  logError(err, vm, info)
}


function logError(err, vm, info) {
  if (process.env.NODE_ENV !== 'production') {
    warn(`Error in ${info}: "${err.toString()}"`, vm)
  }
  if ((inBrowser || inWeex) && typeof console !== 'undefined') {
    console.error(err)
  } else {
    throw err
  }
}

/*
* ErrorBoundary  错误边界
*
* react
* class ErrorBoundary extends React.Component {
*    constructor(props) {
*        super(props);
*        this.state = { hasError: false };
*    }
*    componentDidCatch(error, info) {
*        this.setState({ hasError: true });
*        // 将异常信息上报给服务器
*        logErrorToMyService(error, info);
*    }
*    render() {
*        if (this.state.hasError) {
*            return '出错了';
*        }
*        return this.props.children;
*    }
* }
* vue
* Vue.component('ErrorBoundary', {
*  data: () => ({ error: null }),
*  errorCaptured (err, vm, info) {
*    this.error = `${err.stack}\n\nfound in ${info} of component`
*    return false
*  },
*  render (h) {
*    if (this.error) {
*      return h('pre', { style: { color: 'red' }}, this.error)
*    }
*    // ignoring edge cases for the sake of demonstration
*    return this.$slots.default[0]
*  }
*})
**
* <ErrorBoundary>
*   <this.props.children>
* </ErrorBoundary>
*
* 笔记
*
* 组件内部使用
* errorCaptured(...opt) {
*   //  当前组件报错,他会顺着父组件向上传递,直接到全局的errorHandler
*    console.log('你大爷的', opt);
*    return false   // 错误会被阻止,不会换起上一级的 errorCaptured 和全局的errorHandler
*  },
*
* 全局使用
*Vue.config.errorHandler = function (err, vm, info) {
*  // throw Error('抛出一个错误')
*  let {
*    message, // 异常信息
*    name, // 异常名称
*   stack  // 异常堆栈信息
* } = err;
* console.log('----1111----', name);
* console.log('----1111----', message);
* console.log('----1111----', stack);
*
* console.log('----2222----', vm,);
* console.log('----3333----', info);
* // 可以执行一步操作
};
* throw Error('抛出一个错误')
*
* */
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163

# react的error解析

// koa2
router.post('/errorMsg/', function(ctx) {
    let error = ctx.request.body;; // 获取前端传过来的报错对象
    let url = error.scriptURI; // 压缩文件路径
    if (url) {
        // map文件路径  vue的官方不建议上传 .map 文件,说是容易看到很多源码,react何尝不是呢
        // 所以可以直接传递给服务器当前的 .map 文件
        let fileUrl = url.slice(url.indexOf('client/')) + '.map'; 
        // 解析sourceMap
        let smc = new sourceMap.SourceMapConsumer(fs.readFileSync(resolve('../' + fileUrl), 'utf8')); // 返回一个promise对象
        smc.then(function(result) {
            // 解析原始报错数据
            let ret = result.originalPositionFor({
                line: error.lineNo, // 压缩后的行号
                column: error.columnNo // 压缩后的列号
            });
            let url = ''; // 上报地址
            // 将异常上报至后台
            fetch(url, {
                method: 'POST',
                headers: {
                    'Content-Type': 'application/json'
                },
                body: JSON.stringify({
                    errorMessage: error.errorMessage, // 报错信息
                    source: ret.source, // 报错文件路径
                    line: ret.line, // 报错文件行号
                    column: ret.column, // 报错文件列号
                    stack: error.stack // 报错堆栈
                })
            }).then(function(response) {
                return response.json();
            }).then(function(json) {
                res.json(json);
            });
        })
    }
});
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38