Angular $q Promise的catch/finally不生效,标准Promise正常?求解
这个问题的核心在于Angular 1.5.3的$q和原生Promise对同步代码块中抛出异常的处理逻辑完全不同,咱们一步步拆解原因和解决方案:
为什么原生Promise能正常捕获异常?
当你在new Promise()的executor函数(就是接收resolve/reject的那个回调)里同步抛出异常时,原生Promise的内部机制会自动捕获这个异常,并将其作为Promise的reject原因。相当于原生Promise自动帮你调用了reject(抛出的异常),所以后续的.catch()和.finally()都会正常触发——异常被包裹进了Promise的状态流转里。
看你的standardPromise代码,访问emptyvar.fakeproperty触发的TypeError,直接被原生Promise捕获,变成了Promise的reject信号,自然会走catch分支,最后执行finally。
为什么Angular 1.5.3的$q会让异常冒泡到外部try/catch?
你的angularPromise用的是$q的deferred模式:先创建defer对象,返回它的promise。但问题出在同步抛出异常的代码不在$q的自动捕获范围内——这个异常是在函数执行的同步阶段直接抛出的,而$q的deferred模式不会自动捕获当前函数栈中的同步异常。
也就是说,当你调用angularPromise("fail")时,函数同步执行到var fail = emptyvar.fakeproperty,直接抛出TypeError,这时候Promise还处于pending状态(你甚至没来得及调用defered.reject()),这个异常会直接跳出Promise的上下文,被外部的try/catch捕获。而因为Promise从来没被resolve或reject过,.catch()和.finally()自然不会执行。
针对Angular 1.5.3的解决方案
要让$q的Promise能捕获这个同步异常,有两种常用方法:
方法1:手动用try/catch包裹风险代码,手动触发reject
var angularPromise = function (data) { var defered = $q.defer(); if (data == "fail") { console.log("code reaches this point"); try { var emptyvar = null; var fail = emptyvar.fakeproperty; defered.reject("failed"); } catch(e) { // 把捕获到的异常手动传给defer的reject defered.reject(e); } } else { defered.resolve("success"); } return defered.promise; }
方法2:使用$q的工厂函数(对齐原生Promise写法)
$q的工厂函数写法和原生Promise类似,它会自动捕获executor里的同步异常:
var angularPromise = function (data) { return $q(function(resolve, reject) { var emptyvar = null; if (data == "fail") { console.log("code reaches this point"); var fail = emptyvar.fakeproperty; reject("failed"); } else { resolve("success"); } }); }
这种写法下,$q会像原生Promise一样自动捕获executor里的同步异常,触发后续的.catch()和.finally()。
补充说明
Angular 1.x的$q在早期版本(包括1.5.3)的deferred模式下,确实没有处理同步异常的自动捕获逻辑,这是和原生Promise的一个关键差异。后续Angular的版本(以及Angular 2+借助Zone.js实现的Promise)逐渐对齐了原生Promise的行为,但对于1.5.3这个特定版本,上面的手动处理方法是最直接的解决方案。
内容的提问来源于stack exchange,提问作者Francisco

