プログラミングを行う際、多くの場合、正しい業務説明書や業務プロセスの仕様書を入手しますが、実際の開発には厄介な問題や例外がたくさんあり、これらの例外にはビジネス ユース ケースの例外や技術的な例外が含まれます。ビジネスユースケースの例外については、実装者とユーザーが協力して合理的な解決策を提供することを要求するしかありませんが、技術的な例外については私たちプログラマーが処理する必要があり、これを記録したいと思います。
2つの記事に分ける予定です:「フロントエンド マジック ホール - 例外はトライ/キャッチだけではない」と「フロントエンド マジック ホール - コール スタック、例外インスタンスの宝物」それぞれ組み込み/カスタム例外について説明します。クラスと実行時例外のキャプチャ/構文例外/ネットワーク要求例外/PromiseRejection イベント、コール スタックとは何か、およびコール スタックの関連情報を取得する方法。
出発前から楽しみにしてますか?はい、皆さん、手すりにつかまってください、老ドライバーが運転しようとしています^_^
この記事では次の内容について説明します:
異常またはエラー?それは私たちのコードにどのような影響を与えるでしょうか?
組み込みの例外タイプとは何ですか?
独自の例外タイプを作成してみましょう!
「同期コード」で「ランタイム例外」をキャプチャするには、try/catch
を使用するだけです。 try/catch
就够了。
"万能"异常捕获者window.onerror
,真的万能吗?
Promise.reject也抛异常,怎么办?
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属性
现在我们看看具体有哪些内置的异常类型吧!
EvalError,调用eval()
时发生的异常,已被废弃只用于向后兼容而已
InternalError,JavaScript引擎内部异常,FireFox独门提供的!
RangeError,当函数实参越界时发生,如Array
,Number.toExponential
,Number.toFixed
和Number.toPrecision
时入参非法时。
ReferenceError,当引用未声明的变量时发生
SyntaxError,解析时发生语法错误
TypeError,当值不是所期待的类型时,null.f()
也报这个错
URIError,当传递一个非法的URI给全局URI处理函数时发生,如decodeURIComponent('%')
,即decodeURIComponent
,decodeURI
,encodeURIComponent
,encodeURI
关于在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 エンジンがクラッシュすることはありません。タスク。 🎜 最も一般的な例外は現在実行中のタスクの終了であると上で述べられていますが、これは何を意味しますか?これにはイベント ループの原理が関係します。コードで大まかに説明してみます。 🎜try{ throw Error("unexpected operation happen...") } catch (e){ console.log(e.message) }
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) }
// 非法标识符a->b,真心捕获不到啊亲~! try{ a->b = 1 } catch(e){ console.log(e) }
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 } }
eval()
を呼び出すときに発生する例外である 🎜🎜🎜🎜EvalError は廃止され、下位互換性のためにのみ使用されます 🎜🎜🎜🎜InternalError強い >、JavaScript エンジンの内部例外、FireFox によって独自に提供されます。 🎜🎜🎜🎜RangeError は、Array
、Number.toExponential
、Number などの関数パラメータが範囲外の場合に発生します。 toFixed
code> と Number.toPrecision
は無効なパラメータです。 🎜🎜🎜🎜ReferenceError、宣言されていない変数を参照するときに発生します 🎜🎜🎜🎜SyntaxError、解析中に構文エラーが発生します 🎜🎜🎜🎜TypeError、値が予期された型ではない場合、null.f()
もこのエラーを報告します🎜🎜🎜🎜URIError (不正な URI が処理のためにグローバル URI に渡された場合) decodeURIComponent('%')
などの関数が発生します。つまり、decodeURIComponent
、decodeURI
、encodeURIComponent
、< code> 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中没有这个实参
window.onerror = function(message, source, lineno, colno, error){ // Do something you like. } setTimeout(function(){ throw Error("oh no!") }, 0) a->b = 1
try/catch
を使用してキャッチし、例外を処理します。 🎜// 有异常没问题啊,因为我看不到^_^ window.onerror = function(){return true}
(try (throw (Error. "unexpected operation happen...") (catch e (println (.-message e)))))
很多时我们会以为这样书写就万事大吉了,但其实try/catch
能且仅能捕获“同步代码”中的"运行时异常"。
1."同步代码"就是说无法获取如setTimeout
、Promise
等异步代码的异常,也就是说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~)
Chrome中对于跨域脚本所报的异常,虽然onerror能够捕获,但统一报Script Error
。若要得到正确的错误信息,则要配置跨域资源共享CORS才可以。
window.onerror
实际上采用的事件冒泡的机制捕获异常,并且在冒泡(bubble)阶段时才触发,因此像网络请求异常这些不会冒泡的异常是无法捕获的。
Promise.reject产生的未被catch的异常,window.onerror
也是无能为力。
通过Promise来处理复杂的异步流程控制让我们得心应手,但倘若其中出现异常或Promise实例状态变为rejected时,会是怎样一个状况,我们又可以如何处理呢?
Promise实例的初始化状态是pending,而发生异常时则为rejected,而导致状态从pending转变为rejected的操作有
调用Promise.reject
类方法
在工厂方法中调用reject
方法
在工厂方法或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
若在异常发生前我们没有调用catch
方法来捕获异常,还是可以通过window
的unhandledrejection
事件捕获异常的
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)
另外,还可以通过window
的rejectionhandled
事件监听异步注册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才会触发该事件。
也许我们都遇到<img src="./404.png">
报404网络请求异常的情况,然后测试或用户保障怎么哪个哪个图标没有显示。其实我们我们可以通过以下方式捕获这类异常
window.addEventListener("error", function(e){ // Do something console.log(e.bubbles) // 回显false }, true)
由于网络请求异常不会冒泡,因此必须在capture阶段捕获才可以。但还有一个问题是这种方式无法精确判断异常的HTTP状态是404还是500等,因此还是要配合服务端日志来排查分析才可以。
以上がフロントエンド例外のトライ/キャッチに関する質問の詳細内容です。詳細については、PHP 中国語 Web サイトの他の関連記事を参照してください。