第二十五章 Caché 命令大全 THROW 命令

向下一个异常处理程序显式引发异常。

重点
  1. 推荐使用命令。
大纲
THROW oref

参数

  • oref 可选-抛给异常处理程序的对象引用。可选,但强烈推荐。
描述

Throw命令显式抛出异常。异常可以是系统错误、状态异常或用户定义的异常。它将此异常作为从%Exception.AbstractException对象继承的对象引用(OREF)抛出。Throw命令将此异常抛给下一个异常处理程序。

有两种方法可用来抛出OREF:

  • TRY/CATCH:使用Throw OREFTRY代码块内显式发出异常信号,将执行从TRY块转移到其对应的CATCH块异常处理程序。
  • 其他异常处理程序:当不在try块中时,使用Throw OREF显式地发出异常信号。这将触发当前异常处理程序(例如,$ZTRAP),其中可以从$THROWOBJ特殊变量检索OREF。

注意:不建议在新代码中使用不带参数的抛出。

测试

当发生运行时错误(例如引用未定义的变量)时,Caché会发出系统错误。系统错误生成%Exception.SystemException对象引用,设置OREF属性CodeNameLocationData,还设置$ZERROR$ECODE特殊变量,并将控制转移到下一个错误处理程序。此错误处理程序可以是捕获异常处理程序,也可以是$ZTRAP$ETRAP错误处理程序。系统错误是隐式错误,不使用抛出。

可以在错误处理程序中使用Throw将系统错误对象抛出给另一个错误处理程序。这称为重新发信号通知系统错误。

抛出将控制向上传递到执行堆栈到下一个错误处理程序。如果异常是%Exception.SystemException对象,则下一个错误处理程序可以是任何类型(CATCH$ZTRAP$ETRAP)。否则,必须有一个Catch来处理异常,否则Caché会生成<throw>错误。

参数

oref

对异常对象的引用,%Exception.AbstractException继承的任何类的实例。系统错误的异常对象是类%Exception.SystemException的实例。用户指定的异常对象可以是状态异常对象(%Exception.StatusException)、常规异常对象(%Exception.General)或SQL异常对象(`%Exception.SQL)。用户异常对象的创建和填充由程序员负责。

TryTHROW

可以从TRY块向其对应的CATCH块发出抛出OREF。这会显式发出用户定义异常的信号。这会将执行从TRY块转移到其对应的CATCH块。抛出的OREF被设置为CATCH块的EXCEPTIOVAR参数。

若要从Catch异常处理程序发出带参数的抛出,可以向非Catch异常处理程序抛出,也可以在Catch异常处理程序中嵌套Try块(和关联的嵌套Catch块),然后从此嵌套的Try块发出抛出。

状态异常和用户定义的异常

要捕获状态异常或用户定义的异常,请基于%Exception.AbstractException对象指定一个对象作为OREF参数。定义一个异常类,然后使用%New()创建该类的实例并提供异常信息。这些类型的异常必须由捕获异常处理程序处理。如果不存在Catch,则Caché会生成<throw>错误。

用户定义的异常不会更改$ZERROR$ECODE的值。为了使用这两个特殊变量中的任何一个,程序必须使用SET命令显式设置它们。

一般异常

Caché提供了一个通用异常,可以将其作为抛出参数提供。这是%Exception.AbstractException抽象类的%Exception.General子类。其用法如下例所示:

/// d ##class(PHA.TEST.Command).TestThrow()
ClassMethod TestThrow()
{
	TRY {
		WRITE "在try块中",!!
		SET mygenex = ##class(%Exception.General).%New("我的异常","999",,"我自己的特殊异常")
		THROW mygenex
		WRITE "这应该不会显示",!
	}
	CATCH stuff {
		WRITE "在CATCH块中",!
		WRITE stuff.Name,!
		WRITE stuff.Code,!
		WRITE stuff.Data,!
		WRITE "CATCH块的末尾",!
		RETURN
	}
}
DHC-APP>d ##class(PHA.TEST.Command).TestThrow()try块中
 
在CATCH块中
我的异常
999
我自己的特殊异常
CATCH块的末尾

不在TRY块中时抛出

如果在TRY块之外发出THROW,则Caché会生成错误,如下所示:+ 3 ^ myprog *%Exception.General MyErr 999我的用户定义错误。THROW的这种用法对于重新发出错误信号很有用。

THROW中指定的对象引用(oref)存储在$ THROWOBJ特殊变量中。例如,9@%Exception.General。下一次成功的THROW操作或SET $ THROWOBJ =“”将清除$ THROWOBJ值。

在下面的示例中,THROW$ ZTRAP异常处理程序抛出异常:

/// d ##class(PHA.TEST.Command).TestMainRou()
ClassMethod TestMainRou()
{
	WRITE "在主程序中",!!
	SET $ZTRAP="ErrRou"
	SET mygenex = ##class(%Exception.General).%New("My exception","999",,
                             "My own special exception")
	THROW mygenex
	WRITE "这不应该显示",!
	RETURN
ErrRou
   WRITE "在$ZTRAP",!
   SET oref=$THROWOBJ
   SET $THROWOBJ=""
   WRITE oref.Name,!
   WRITE oref.Code,!
   WRITE oref.Data,!
   WRITE "结束$ZTRAP",!
   RETURN
}
DHC-APP>d ##class(PHA.TEST.Command).TestMainRou()
在主程序中
 
在$ZTRAP
My exception
999
My own special exception
结束$ZTRAP

无参数THROW

无参数的THROW重新发出当前系统错误的信号,将控制权转移到下一个异常处理程序。当前系统错误是$ ZERROR特殊变量引用的错误。因此,无参数的THROW等效于命令ZTRAP $ ZERROR

不建议使用无参数抛出,因为哪个系统错误是当前系统错误可能会改变。例如,如果错误处理程序更改$ZERROR值,或者错误处理程序本身生成系统错误,就会发生这种情况。因此,最好使用Throw OREF显式指定要抛出给下一个异常处理程序的系统错误。

示例

请注意,在这些示例中使用了$ZCVT(myerr.Name,"O","HTML"),因为Caché错误名称包含在尖括号中,并且这些示例是从Web浏览器运行的。在大多数其他情况下,myerr.Name将返回所需的值。

下面的示例使用%Exception.General类的实例引发用户定义的异常。它在try块中生成出生日期;如果它生成将来的出生日期,则使用Throw发出一般异常,并将用户定义的异常的OREF传递给通用CATCH块。(可能需要多次运行此示例才能生成引发异常的日期):

/// d ##class(PHA.TEST.Command).TestThrowBro()
ClassMethod TestThrowBro()
{
	TRY {
		WRITE "在TRY块中",!
		SET badDOB=##class(%Exception.General).%New("<BAD DOB>","999",,"出生日期是未来")
		FOR x=1:1:20 { 
			SET rndDOB = $RANDOM(7)_$RANDOM(10000)
			IF rndDOB > $HOROLOG { 
				WRITE !,"出生日期 ",$ZDATE(rndDOB,1,,4)," 是无效"
				THROW badDOB 
			}
			ELSE { 
				WRITE "出生日期 ",$ZDATE(rndDOB,1,,4)," 有效",! 
			}
		}
	}
	CATCH err {
		WRITE !,"在CATCH块中"
		WRITE !,"Error code=",err.Code
		WRITE !,"Error name=",$ZCVT(err.Name,"O","HTML")
		WRITE !,"Error data=",err.Data
		RETURN
	}
}
DHC-APP>d ##class(PHA.TEST.Command).TestThrowBro()
在TRY块中
出生日期 03/06/1860 有效
出生日期 07/05/1852 有效
出生日期 07/29/1987 有效
出生日期 02/25/1847 有效
出生日期 07/09/1945 有效
出生日期 05/09/1877 有效
出生日期 02/18/1865 有效
出生日期 02/13/1900 有效
出生日期 11/16/1959 有效
出生日期 03/21/1965 有效
 
出生日期 04/18/2031 是无效
在CATCH块中
Error code=999
Error name=&lt;BAD DOB&gt;
Error data=出生日期是未来

以下示例可以使用用户定义的参数发出两个THROW命令之一。$RANDOM选择要发出的THROW(随机值0或1)或不发出THROW(随机值2)。请注意,代码执行在TRY / CATCH块对之后继续,除非块执行以RETURN命令结束:

/// d ##class(PHA.TEST.Command).TestThrowZero()
ClassMethod TestThrowZero()
{
	TRY {
		SET errdatazero="这是零错误"
		SET errdataone="这是一个错误"
		/* Error Randomizer */
		SET test=$RANDOM(3)
		WRITE "错误测试是 ",test,!
		IF test=0 { 
			WRITE !,"Throwing exception 998",!
			THROW ##class(Sample.MyException).%New("TestZeroError",998,,errdatazero)
			THROW myvar
		}
		ELSEIF test=1 { 
			WRITE !,"Throwing exception 999",!
			THROW ##class(Sample.MyException).%New("TestOneError",999,,errdataone)
		}
		ELSE { 
			WRITE !,"这次没有THROW错误" 
		}
	}
	CATCH exp {
		WRITE !,"这是异常处理程序"
		WRITE !,"Error code=",exp.Code
		WRITE !,"Error name=",exp.Name
		WRITE !,"Error data=",exp.Data
		RETURN
	}
	WRITE !!,"TRY块之后的执行在这里继续"
}
DHC-APP>d ##class(PHA.TEST.Command).TestThrowZero()
错误测试是 0
 
Throwing exception 998
 
这是异常处理程序
Error code=998
Error name=TestZeroError
Error data=这是零错误
DHC-APP>d ##class(PHA.TEST.Command).TestThrowZero()
错误测试是 1
 
Throwing exception 999
 
这是异常处理程序
Error code=999
Error name=TestOneError
Error data=这是一个错误
DHC-APP>d ##class(PHA.TEST.Command).TestThrowZero()
错误测试是 2
 
这次没有THROW错误
 
TRY块之后的执行在这里继续

以下示例显示THROW的使用和系统错误。在CATCH异常处理程序中通常使用THROW将系统错误转发到另一个处理程序。当收到的系统错误是系统错误的意外类型时,可能会发生这种情况。请注意,这需要将TRY块(和相应的CATCH块)嵌套在CATCH块中。它用于将系统错误扔给嵌套的CATCH块。在下面的示例中显示了此示例,该示例调用Calculate执行除法运算并返回答案。有三种可能的结果:如果y =任何非零数,则除法运算成功,并且不执行CATCH块代码。如果y = 0(或任何非数字字符串),则除法运算将尝试除以零,从而在其CATCH块中引发系统错误;否则,系统将对其进行除法。这由calcerr异常处理程序捕获,该处理程序“更正”了该错误并返回值0。但是,如果未定义y(NEW y,则calcerr捕获意外的系统错误,并将此错误引发给myerr异常处理程序。为了演示这三种可能的结果,此示例程序使用$ RANDOM设置除数(y):

Randomizer
	SET test=$RANDOM(3)
	IF test=0 { 
		SET y=0 
	}
	ELSEIF test=1 { 
		SET y=7 
	}
	ELSEIF test=2 { 
		NEW y 
	}
	/* 注意:如果test = 2,则y未定义 */
Main5
	SET x=4
	TRY {
		SET result=$$Calculate(x,y)
		WRITE !,"Calculated value=",result
	}
	CATCH myerr {  
		WRITE !,"这是异常处理程序"
		WRITE !,"Error code=",myerr.Code
		WRITE !,"Error name=",$ZCVT(myerr.Name,"O","HTML")
		WRITE !,"Error data=",myerr.Data
	}
	QUIT

Calculate(arg1,arg2) PUBLIC {
	TRY {
		SET answer=arg1/arg2
	}
	CATCH calcerr {
		WRITE "在CATCH块中",!
		TRY {
			IF calcerr.Name="<DIVIDE>" {
				WRITE !,"处理除0错误"
				SET answer=0 
			}
			ELSE { 
				THROW calcerr 
			}
			RETURN
		}
		CATCH {
			WRITE "意外的错误",!
			WRITE "Error name=",$ZCVT(myerr.Name,"O","HTML"),!
		}
	}
	QUIT answer
}
DHC-APP>d Randomizer^PHA.TEST.Command
 
Calculated value=.5714285714285714286
DHC-APP>d Randomizer^PHA.TEST.Command
 
这是异常处理程序
Error code=9
Error name=&lt;UNDEFINED&gt;
Error data=y
DHC-APP>d Randomizer^PHA.TEST.Command
在CATCH块中
 
处理除0错误
这是异常处理程序
Error code=101
Error name=&lt;COMMAND&gt;
Error data=Function must return a value at ^PHA.TEST.Command