Maison >interface Web >js tutoriel >Questions sur l'exception frontale try/catch

Questions sur l'exception frontale try/catch

一个新手
一个新手original
2017-10-26 10:06:523220parcourir

Avant-propos

Lors de la programmation, nous obtenons souvent des documents de description commerciale ou des spécifications correctes pour les processus métier, mais le développement réel est plein d'épines et d'exceptions, et ces exceptions incluent également des exceptions pour les cas d'utilisation commerciale. compris. Pour les exceptions aux cas d'utilisation commerciale, nous n'avons d'autre choix que d'exiger que les implémenteurs et les utilisateurs travaillent ensemble pour fournir des solutions raisonnables ; tandis que les exceptions techniques doivent être gérées par nous, les programmeurs, et c'est ce que je souhaite enregistrer.
Je prévois de le diviser en deux articles : "Front-end Magic Hall - Les exceptions ne sont pas seulement try/catch" et "Front-end Magic Hall - Call stack, le trésor dans les instances d'exception", décrivant respectivement le construit- dans/classes d'exception personnalisées et capture des exceptions d'exécution/exceptions de syntaxe/exceptions de demande réseau/événements PromiseRejection, qu'est-ce qu'une pile d'appels et comment obtenir des informations relatives à la pile d'appels.
Vous l'attendez déjà avec impatience avant de partir ? D'accord, tout le monde, accrochez-vous aux rampes, l'ancien conducteur est sur le point de conduire ^_^

Résumé

Cet article décrira le contenu suivant :

  1. Anormalité ou pas erreur ? Comment cela affectera-t-il notre code ?

  2. Quels sont les types d'exceptions intégrés ?

  3. Écrivons votre propre type d'exception !

  4. Pour capturer les "exceptions d'exécution" dans le "code synchrone", utiliser try/catch suffit.

  5. Le receveur d'exceptions "universel" window.onerror, est-il vraiment tout-puissant ?

  6. Promise.reject lève également une exception, que dois-je faire ?

  7. 404 et autres requêtes réseau anormales, voulez-vous vraiment vous réveiller plus tard ?

1. Anomalie ou erreur ? Comment cela affectera-t-il notre code ?

Lors de l'apprentissage de Java, on nous dira que les exceptions (Exception) et les erreurs (Error) sont différentes. Les exceptions ne provoqueront pas l'arrêt du processus et peuvent être réparées (try/catch), mais les erreurs provoqueront le problème. le processus est terminé et ne peut donc pas être réparé. En ce qui concerne JavaScript, nous n'avons à faire face qu'à des exceptions (bien que le nom de la classe d'exception soit Error ou contienne le mot Error). L'apparition d'exceptions ne provoquera pas le crash du moteur JavaScript, tout au plus mettra-t-elle fin à l'exécution en cours. tâche.
Il est mentionné ci-dessus que l'exception la plus courante est la fin de la tâche en cours d'exécution. Qu'est-ce que cela signifie ? Cela implique le principe d'Event Loop. Laissez-moi essayer de l'expliquer grossièrement avec du code.

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

2. Quels sont les types d'exceptions intégrés ?

En parlant de classes d'exception intégrées, la première chose à mentionner est le type d'ancêtre Error Toutes les autres classes d'exception intégrées et classes personnalisées doivent en hériter. Ses attributs et méthodes standard sont simplement les suivants :

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

Comme il y a trop peu d'attributs standard, il ne peut pas fournir des informations plus efficaces aux développeurs pour localiser l'emplacement de l'anomalie et reproduire la scène de l'accident. chaque fabricant de navigateur a ajouté de nombreux attributs, puis est progressivement devenu le standard de facto.

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

De plus, Juhard a également ajouté les deux attributs suivants

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

Alors maintenant, je veux instancier un objet Error, appelez simplement Error() ou new Error() ; souhaitez définir un message en même temps, remplacez-le par Error("test") ou new Error("test"). En fait, la signature du constructeur d'Erreur ressemble à ceci

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

Jetons maintenant un coup d'œil aux types d'exceptions intégrés spécifiques !

  1. EvalError, une exception qui se produit lors de l'appel de eval(), est obsolète et n'est utilisée que pour des raisons de compatibilité ascendante

  2. InternalError, exception interne du moteur JavaScript, fournie uniquement par FireFox !

  3. RangeError se produit lorsque le paramètre réel de la fonction sort des limites, par exemple lorsque le paramètre d'entrée est illégal dans Array, Number.toExponential, Number.toFixed et Number.toPrecision.

  4. ReferenceError, se produit lors du référencement d'une variable non déclarée

  5. SyntaxError, lors de l'analyse d'une syntaxe l'erreur

  6. TypeError se produit lorsque la valeur n'est pas le type attendu, null.f() signale également cette erreur

  7. URIError, se produit lorsqu'un URI illégal est transmis à la fonction de gestion d'URI globale, telle que decodeURIComponent('%'), c'est-à-dire decodeURIComponent, decodeURI, encodeURIComponent, encodeURI

3. Commencez à écrire votre propre type d'exception !

Quelqu'un a déjà expliqué comment personnaliser les types d'exceptions sur StackOverflow
Nous pouvons donc simplement suivre

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 est implémenté comme suit

(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. Pour capturer les "exceptions d'exécution" dans le "code synchrone", utilisez

suffit try/catch

Afin d'éviter le risque que le code normal soit ignoré en raison de l'apparition d'exceptions, nous avons l'habitude de prendre

pour détecter et gérer les exceptions. try/catch

try{
  throw Error("unexpected operation happen...")
}
catch (e){
  console.log(e.message)
}
Méthode d'écriture 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等,因此还是要配合服务端日志来排查分析才可以。


Ce qui précède est le contenu détaillé de. pour plus d'informations, suivez d'autres articles connexes sur le site Web de PHP en chinois!

Déclaration:
Le contenu de cet article est volontairement contribué par les internautes et les droits d'auteur appartiennent à l'auteur original. Ce site n'assume aucune responsabilité légale correspondante. Si vous trouvez un contenu suspecté de plagiat ou de contrefaçon, veuillez contacter admin@php.cn