Home  >  Article  >  Web Front-end  >  Questions about front-end exception try/catch

Questions about front-end exception try/catch

一个新手
一个新手Original
2017-10-26 10:06:523130browse

Preface

When programming, what we often get is the correct business description document or specification of the business process, but the actual development is full of thorns and exceptions, and these exceptions include exceptions for business use cases. Technical exceptions are also included. For exceptions to business use cases, we have no choice but to require implementers and users to work together to provide reasonable solutions; while technical exceptions must be handled by us programmers, and this is what I want to record.
I plan to divide it into two articles: "Front-end Magic Hall - Exceptions are not just try/catch" and "Front-end Magic Hall - Call stack, treasures in exception instances" respectively describing built-in/custom exception classes and capturing runtime Exceptions/syntax exceptions/network request exceptions/PromiseRejection events, what is a call stack and how to obtain call stack related information.
Are you already looking forward to it before setting off? Okay, everyone, hold on to the handrails, the old driver is about to drive^_^

Summary

This article will describe the following content:

  1. Abnormality or mistake? How will it affect our code?

  2. What are the built-in exception types?

  3. Let’s write our own exception types!

  4. To capture "Runtime Exceptions" in "Synchronization Code", try/catch is enough.

  5. "Universal" exception catcherwindow.onerror, is it really omnipotent?

  6. Promise.reject also throws an exception, what should I do?

  7. 404 Do you really want to wait until the network request is abnormal?

1. Abnormality or error? How will it affect our code?

When learning Java, we will be told that exceptions (Exception) and errors (Error) are different. Exceptions will not cause the process to terminate and can be repaired (try/catch), but errors will cause The process terminated and therefore cannot be repaired. When it comes to JavaScript, all we have to face are exceptions (although the exception class name is Error or contains the word Error). The occurrence of exceptions will not cause the JavaScript engine to crash. At most, it will terminate the currently executing task.
It is mentioned above that the most common exception is to terminate the currently executing task. What does it mean? This involves the principle of Event Loop. Let me try to explain it roughly with code.

<script>
  // 1.当前代码块将作为一个任务压入任务队列中,JavaScript线程会不断地从任务队列中提取任务执行;
  // 2.当任务执行过程中报异常,且异常没有捕获处理,则会一路沿着调用栈从顶到底抛出,最终终止当前任务的执行;
  // 3.JavaScript线程会继续从任务队列中提取下一个任务继续执行。
  function a(){throw Error("test")}
  function b(){a()}
  b()
  console.log("永远不会执行!")
</script>
<script>
  // 下一个任务
  console.log("你有你抛异常,我照样执行!")
</script>

2. What are the built-in exception types?

Speaking of built-in exception classes, the first thing to mention is the ancestor type Error. All other built-in exception classes and custom classes must inherit it. And its standard properties and methods are just the following.

@prop {String} name - 异常名称
@prop {String} message - 供人类阅读的异常信息
@prop {Function} constructor - 类型构造器
@method toString():String - 输出异常信息

Because there are too few standard properties, it cannot provide more effective information for developers to locate the location of the abnormality and reproduce the accident scene, so each Browser manufacturers have added a lot of attributes themselves, and then gradually became the de facto standard.

@prop {String} fileName - 异常发生的脚本URI
@prop {number} lineNumber - 异常发生的行号
@prop {number} columnNumber - 异常发生的列号
@prop {String} stack - 异常发生时的调用栈信息,IE10及以上才支持
@method toSource():String - 异常发生的脚本内容

In addition, Juhard has also added the following two attributes

@prop {String} description - 和message差不多
@prop {number} number - 异常类型的编号,巨硬为每个异常设置了一个唯一的编号

So now I want to instantiate an Error object, just call Error() or new Error() will do; if you want to set the message at the same time, change it to Error("test") or new Error("test"). In fact, the constructor signature of Error is like this

@constructor
@param {String=} message - 设置message属性
@param {String=} fileName - 设置fileName属性
@param {number=} lineNumber - 设置lineNUmber属性

Now let’s take a look at the specific built-in exception types!

  1. EvalError, an exception that occurs when calling eval(), has been abandoned and is only used for backward compatibility

  2. InternalError, JavaScript engine internal exception, uniquely provided by FireFox!

  3. RangeError, occurs when the function parameter is out of bounds, such as Array, Number.toExponential, Number.toFixed and Number.toPrecision are invalid parameters.

  4. ReferenceError, occurs when an undeclared variable is referenced

  5. SyntaxError, when parsing A syntax error

  6. TypeError occurs. When the value is not the expected type, null.f() also reports this error

  7. URIError, occurs when an illegal URI is passed to the global URI processing function, such as decodeURIComponent('%'), that is, decodeURIComponent , decodeURI,encodeURIComponent, encodeURI

##3. Write your own exception type!

Some people have discussed how to customize exception types on StackOverflow for reference

So we can just pick it up

function MyError(message, fileName, lineNumber){
  if (this instanceof MyError);else return new MyError(message, fileName, lineNumber)
  this.message = message || ""
  if (fileName){ this.fileName = fileName }
  if (lineNumber){ this.lineNumber = lineNumber }
}

var proto = MyError.prototype = Object.create(Error.prototype)
proto.name = "MyError"
proto.constructor = MyError

cljs is implemented as follows

(defn ^export MyError [& args]
  (this-as this
    (if (instance? MyError this)
      (let [ps ["message" "fileName" "lineNumber"]
            idxs (-> (min (count args) (count ps)) range)]
        (reduce
          (fn [accu i]
            (aset accu (nth ps i) (nth args i))
            accu)
          this
          idxs))
      (apply new MyError args))))

(def proto
  (aset MyError "prototype" (.create js/Object (.-prototype Error))))
(aset proto "name" "MyError")
(aset proto "constructor" MyError)

4. Capture For "Runtime Exception" in "Synchronization Code", it is enough to use

try/catch

In order to prevent the risk of normal code being skipped due to the occurrence of exceptions, we are used to taking

try/catchTo catch and handle exceptions.

try{
  throw Error("unexpected operation happen...")
}
catch (e){
  console.log(e.message)
}

cljs writing method

(try
  (throw (Error. "unexpected operation happen...")
  (catch e
         (println (.-message e)))))

 很多时我们会以为这样书写就万事大吉了,但其实try/catch能且仅能捕获“同步代码”中的"运行时异常"。
1."同步代码"就是说无法获取如setTimeoutPromise等异步代码的异常,也就是说try/catch仅能捕获当前任务的异常,setTimeout等异步代码是在下一个EventLoop中执行。

// 真心捕获不到啊亲~!
try{
  setTimeout(function(){
    throw Error("unexpected operation happen...")
  }, 0)
} catch(e){
  console.log(e)
}

2."运行时异常"是指非SyntaxError,也就是语法错误是无法捕获的,因为在解析JavaScript源码时就报错了,还怎么捕获呢~~

// 非法标识符a->b,真心捕获不到啊亲~!
try{
  a->b = 1
} catch(e){
  console.log(e)
}

 这时大家会急不可待地问:“异步代码的异常咋办呢?语法异常咋办呢?”在解答上述疑问前,我们先偏离一下,稍微挖挖throw语句的特性。

throw后面可以跟什么啊?

 一般而言我们会throw一个Error或其子类的实例(如throw Error()),其实我们throw任何类型的数据(如throw 1,throw "test",throw true等)。但即使可以抛出任意类型的数据,我们还是要坚持抛出Error或其子类的实例。这是为什么呢?

try{
  throw "unexpected operation happen..."
} catch(e){
  console.log(e)
}

try{
  throw TypeError("unexpected operation happen...")
} catch(e){
  if ("TypeError" == e.name){
    // Do something1
  }
  else if ("RangeError" == e.name){
    // Do something2
  }
}

 原因显然易见——异常发生时提供信息越全越好,更容易追踪定位重现问题嘛!

五."万能"异常捕获者window.onerror,真的万能吗?

 在每个可能发生异常的地方都写上try/catch显然是不实际的(另外还存在性能问题),即使是罗嗦如Java我们开发时也就是不断声明throws,然后在顶层处理异常罢了。那么,JavaScript中对应的顶层异常处理入口又在哪呢?木有错,就是在window.onerror。看看方法签名吧

@description window.onerror处理函数
@param {string} message - 异常信息"
@param {string} source  - 发生异常的脚本的URI
@param {number} lineno  - 发生异常的脚本行号
@param {number} colno   - 发生异常的脚本列号
@param {?Error} error   - Error实例,Safari和IE10中没有这个实参

 这时我们就可以通过它捕获除了try/catch能捕获的异常外,还可以捕获setTimeout等的异步代码异常,语法错误。

window.onerror = function(message, source, lineno, colno, error){
  // Do something you like.
}

setTimeout(function(){ throw Error("oh no!") }, 0)
a->b = 1

 这样就满足了吗?还没出大杀技呢——屏蔽异常、屏蔽、屏~~
 只有onerror函数返回true时,异常就不会继续向上抛(否则继续上抛就成了Uncaught Error了)。

// 有异常没问题啊,因为我看不到^_^
window.onerror = function(){return true}

 现在回到标题的疑问中,有了onerror就可以捕获所有异常了吗?答案又是否定的(我的娘啊,还要折腾多久啊~0~)

  1. Chrome中对于跨域脚本所报的异常,虽然onerror能够捕获,但统一报Script Error。若要得到正确的错误信息,则要配置跨域资源共享CORS才可以。

  2. window.onerror实际上采用的事件冒泡的机制捕获异常,并且在冒泡(bubble)阶段时才触发,因此像网络请求异常这些不会冒泡的异常是无法捕获的。

  3. Promise.reject产生的未被catch的异常,window.onerror也是无能为力。

六.Promise.reject也抛异常,怎么办?

 通过Promise来处理复杂的异步流程控制让我们得心应手,但倘若其中出现异常或Promise实例状态变为rejected时,会是怎样一个状况,我们又可以如何处理呢?

Promise是如何标识异常发生的?

 Promise实例的初始化状态是pending,而发生异常时则为rejected,而导致状态从pending转变为rejected的操作有

  1. 调用Promise.reject类方法

  2. 在工厂方法中调用reject方法

  3. 在工厂方法或then回调函数中抛异常

// 方式1
Promise.reject("anything you want")

// 方式2
new Promise(function(resolve, reject) { reject("anything you want") })

// 方式3
new Promise(function{ throw "anything you want" })
new Promise(function(r) { r(Error("anything you want" ) }).then(function(e) { throw e })

 当Promise实例从pending转变为rejected时,和之前谈论到异常一样,要么被捕获处理,要么继续抛出直到成为Uncaught(in promise) Error为止。

异常发生前就catch

 若在异常发生前我们已经调用catch方法来捕获异常,那么则相安无事

new Promise(function(resolve, reject){
  setTimeout(reject, 0)
}).catch(function(e){
  console.log("catch")
  return "bingo"
}).then(function(x){
  console.log(x)
})

// 回显 bingo

专属于Promise的顶层异常处理

 若在异常发生前我们没有调用catch方法来捕获异常,还是可以通过windowunhandledrejection事件捕获异常的

window.addEventListener("unhandledrejection", function(e){
  // Event新增属性
  // @prop {Promise} promise - 状态为rejected的Promise实例
  // @prop {String|Object} reason - 异常信息或rejected的内容

  // 会阻止异常继续抛出,不让Uncaught(in promise) Error产生
  e.preventDefault()
})

迟来的catch

 由于Promise实例可异步订阅其状态变化,也就是可以异步注册catch处理函数,这时其实已经抛出Uncaught(in promise) Error,但我们依然可以处理

var p = new Promise(function(resolve, reject){
  setTimeout(reject, 0)
})
setTimeout(function(){
  p.catch(function(e){
    console.log("catch")
    return "bingo"
  })
}, 1000)

 另外,还可以通过windowrejectionhandled事件监听异步注册catch处理函数的行为

window.addEventListener("rejectionhandled", function(e){
  // Event新增属性
  // @prop {Promise} promise - 状态为rejected的Promise实例
  // @prop {String|Object} reason - 异常信息或rejected的内容

  // Uncaught(in promise) Error已经抛出,所以这句毫无意义^_^
  e.preventDefault()
})

注意:只有抛出Uncaught(in promise) Error后,异步catch才会触发该事件。

七.404等网络请求异常真心要后之后觉吗?

 也许我们都遇到482c199bb16757e6448cd883ac614811报404网络请求异常的情况,然后测试或用户保障怎么哪个哪个图标没有显示。其实我们我们可以通过以下方式捕获这类异常

window.addEventListener("error", function(e){
  // Do something
  console.log(e.bubbles) // 回显false
}, true)

 由于网络请求异常不会冒泡,因此必须在capture阶段捕获才可以。但还有一个问题是这种方式无法精确判断异常的HTTP状态是404还是500等,因此还是要配合服务端日志来排查分析才可以。


The above is the detailed content of Questions about front-end exception try/catch. For more information, please follow other related articles on the PHP Chinese website!

Statement:
The content of this article is voluntarily contributed by netizens, and the copyright belongs to the original author. This site does not assume corresponding legal responsibility. If you find any content suspected of plagiarism or infringement, please contact admin@php.cn