>웹 프론트엔드 >JS 튜토리얼 >프런트엔드 예외 try/catch에 대한 질문

프런트엔드 예외 try/catch에 대한 질문

一个新手
一个新手원래의
2017-10-26 10:06:523219검색

머리말

프로그래밍을 하다 보면 올바른 비즈니스 설명 문서나 비즈니스 프로세스에 대한 사양을 얻는 경우가 많지만 실제 개발에는 가시성과 예외가 가득하며 이러한 예외에는 비즈니스 사용 사례에 대한 예외와 기술적인 예외가 포함됩니다. 비즈니스 사용 사례에 대한 예외의 경우 구현자와 사용자가 함께 협력하여 합리적인 솔루션을 제공하도록 요구할 수밖에 없지만, 기술적 예외는 우리 프로그래머가 처리해야 하며 이것이 제가 기록하고 싶은 것입니다.
내장/사용자 정의 예외를 각각 설명하는 "프런트 엔드 매직 홀 - 예외는 단지 try/catch가 아닙니다"와 "프런트 엔드 매직 홀 - 호출 스택, 예외 인스턴스의 보물"이라는 두 개의 기사로 나눌 계획입니다. 클래스 및 런타임 예외 캡처/구문 예외/네트워크 요청 예외/PromiseRejection 이벤트, 호출 스택이 무엇인지, 호출 스택 관련 정보를 얻는 방법.
출발하기도 전부터 벌써 기대가 되시나요? 자 여러분 난간을 잡으세요 낡은 운전자가 운전을 하게 됩니다^_^

요약

이 글에서는 다음과 같은 내용을 설명합니다.

  1. 비정상인가요 아니면 오류인가요? 우리 코드에 어떤 영향을 미칠까요?

  2. 내장된 예외 유형은 무엇인가요?

  3. 자신만의 예외 유형을 작성해 보세요!

  4. "동기 코드"에서 "런타임 예외"를 캡처하려면 try/catch를 사용하세요. try/catch就够了。

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

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

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

一.异常还是错误?它会如何影响我们的代码?

 在学习Java时我们会被告知异常(Exception)和错误(Error)是不一样的,异常是不会导致进程终止从而可以被修复(try/catch),但错误将会导致进程终止因此不能被修复。当对于JavaScript而言,我们要面对的仅仅有异常(虽然异常类名为Error或含Error字样),异常的出现不会导致JavaScript引擎崩溃,最多就是让当前执行的任务终止而已。
 上面说到异常的出现最多就是让当前执行的任务终止,到底是什么意思呢?这里就涉及到Event Loop的原理了,下面我尝试用代码大致说明吧。

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

二.内置异常类型有哪些?

 说到内置异常类那么必先提到的就是Error这个祖先类型了,其他所有的内置异常类和自定义类都必须继承它。而它的标准属性和方法就以下这寥寥几个而已

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

 由于标准属性实在太少,无法提供更有效的信息供开发者定位异常发生的位置和重现事故现场,因此各浏览器厂家均手多多的自己增加些属性,然后逐渐成了事实标准。

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

另外巨硬还新增以下两个属性

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

 那么现在我要实例化一个Error对象,只需调用Error()new Error()即可;若想同时设置message,则改为Error("test")new Error("test")。其实Error的构造函数签名是这样的

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

现在我们看看具体有哪些内置的异常类型吧!

  1. EvalError,调用eval()时发生的异常,已被废弃只用于向后兼容而已

  2. InternalError,JavaScript引擎内部异常,FireFox独门提供的!

  3. RangeError,当函数实参越界时发生,如ArrayNumber.toExponential,Number.toFixedNumber.toPrecision时入参非法时。

  4. ReferenceError,当引用未声明的变量时发生

  5. SyntaxError,解析时发生语法错误

  6. TypeError,当值不是所期待的类型时,null.f()也报这个错

  7. URIError,当传递一个非法的URI给全局URI处理函数时发生,如decodeURIComponent('%'),即decodeURIComponentdecodeURI,encodeURIComponentencodeURI

三.动手写自己的异常类型吧!

 关于在StackOverflow上早有人讨论如何自定义异常类型了参考
于是我们顺手拈来即可

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实现如下

(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)

四.捕获“同步代码”中的"运行时异常",用try/catch就够了

 为了防止由于异常的出现,导致正常代码被略过的风险,我们习惯采取try/catch

"범용" 예외 포수 window.onerror, 정말 전능한가요? 🎜🎜🎜🎜Promise.reject에서도 예외가 발생합니다. 어떻게 해야 하나요? 🎜🎜🎜🎜404 및 기타 비정상적인 네트워크 요청, 나중에 일어나시겠습니까? 🎜🎜🎜1. 이상인가, 오류인가? 우리 코드에 어떤 영향을 미칠까요? 🎜🎜 Java를 배울 때 예외(Exception)와 오류(Error)는 다르다는 것을 알게 됩니다. 예외는 프로세스를 종료시키지 않으며 복구(try/catch)할 수 있지만 오류로 인해 프로세스가 종료되고 오류가 발생합니다. 그러므로 수리가 불가능합니다. JavaScript의 경우, 우리가 직면해야 하는 것은 예외뿐입니다(예외 클래스 이름이 Error이거나 Error라는 단어를 포함하더라도). 예외가 발생해도 JavaScript 엔진이 중단되지는 않습니다. 기껏해야 현재 실행 중인 엔진이 종료됩니다. 일. 🎜 위에서 예외가 발생하면 현재 실행 중인 작업이 종료된다는 것이 무슨 뜻인가요? 여기에는 Event Loop의 원리가 포함됩니다. 코드를 통해 대략적으로 설명하겠습니다. 🎜
try{
  throw Error("unexpected operation happen...")
}
catch (e){
  console.log(e.message)
}
🎜2. 내장된 예외 유형은 무엇인가요? 🎜🎜 내장 예외 클래스의 경우 상위 유형 Error를 먼저 언급해야 하며 다른 모든 내장 예외 클래스와 사용자 정의 클래스는 이를 상속해야 합니다. 그리고 그 표준 속성과 메소드는 바로 다음과 같습니다🎜
(try
  (throw (Error. "unexpected operation happen...")
  (catch e
         (println (.-message e)))))
🎜 표준 속성이 너무 적어 개발자들이 이상 위치를 파악하고 사고 현장을 재현하는데 보다 효과적인 정보를 제공할 수 없기 때문에, 각 브라우저 제조사의 손길이 더 있으신 분들은, 몇 가지 속성을 추가하면 점차 사실상의 표준이 됩니다. 🎜
// 真心捕获不到啊亲~!
try{
  setTimeout(function(){
    throw Error("unexpected operation happen...")
  }, 0)
} catch(e){
  console.log(e)
}
🎜또한 Juhard는 다음 두 가지 속성도 추가했습니다🎜
// 非法标识符a->b,真心捕获不到啊亲~!
try{
  a->b = 1
} catch(e){
  console.log(e)
}
🎜 이제 Error 객체를 인스턴스화하고 싶습니다. Error() 또는 new Error()를 호출하세요. >그렇습니다. 동시에 메시지를 설정하려면 Error("test") 또는 new Error("test")로 변경하세요. 실제로 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
  }
}
🎜이제 구체적인 내장 예외 유형을 살펴보겠습니다! 🎜🎜🎜🎜EvalError, eval()를 호출할 때 발생하는 예외는 폐기되었으며 이전 버전과의 호환성을 위해서만 사용됩니다. 🎜🎜🎜🎜InternalError Strong >, FireFox에서 고유하게 제공하는 JavaScript 엔진 내부 예외입니다! 🎜🎜🎜🎜RangeErrorArray, Number.toExponential, Number와 같이 함수 매개변수가 범위를 벗어날 때 발생합니다. toFixed code> 및 Number.toPrecision은 잘못된 매개변수입니다. 🎜🎜🎜🎜ReferenceError, 선언되지 않은 변수를 참조할 때 발생 🎜🎜🎜🎜SyntaxError, 구문 분석 중 구문 오류 발생 🎜🎜🎜🎜TypeError, 값이 예상된 유형이 아닐 때 null.f()는 처리를 위해 잘못된 URI가 전역 URI에 전달될 때 이 오류🎜🎜🎜🎜URIError도 보고합니다. decodeURIComponent('%'), 즉 decodeURIComponent, decodeURI, encodeURIComponent, encodeURI🎜🎜🎜3. 자신만의 예외 유형을 작성하세요! 🎜🎜 누군가가 참조를 위해 StackOverflow에서 예외 유형을 사용자 정의하는 방법을 이미 논의했습니다🎜그래서 우리는 쉽게 집어 들었습니다🎜
@description window.onerror处理函数
@param {string} message - 异常信息"
@param {string} source  - 发生异常的脚本的URI
@param {number} lineno  - 发生异常的脚本行号
@param {number} colno   - 发生异常的脚本列号
@param {?Error} error   - Error实例,Safari和IE10中没有这个实参
🎜cljs는 다음과 같이 구현됩니다🎜
window.onerror = function(message, source, lineno, colno, error){
  // Do something you like.
}

setTimeout(function(){ throw Error("oh no!") }, 0)
a->b = 1
🎜4. "동기화 코드"에서 "런타임 예외"를 캡처하려면 다음을 사용하세요. try/catch이면 충분합니다🎜🎜 예외 발생으로 인해 일반 코드를 건너뛰는 위험을 방지하기 위해 우리는 try/catch를 사용하여 catch하고 예외를 처리합니다. 🎜
// 有异常没问题啊,因为我看不到^_^
window.onerror = function(){return true}
🎜cljs 작성법🎜
(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等,因此还是要配合服务端日志来排查分析才可以。


위 내용은 프런트엔드 예외 try/catch에 대한 질문의 상세 내용입니다. 자세한 내용은 PHP 중국어 웹사이트의 기타 관련 기사를 참조하세요!

성명:
본 글의 내용은 네티즌들의 자발적인 기여로 작성되었으며, 저작권은 원저작자에게 있습니다. 본 사이트는 이에 상응하는 법적 책임을 지지 않습니다. 표절이나 침해가 의심되는 콘텐츠를 발견한 경우 admin@php.cn으로 문의하세요.