第二章 Caché 命令大全 CATCH 命令

236 阅读8分钟

第二章 Caché 命令大全 CATCH 命令

标识发生异常时要执行的代码块。

重点

  1. 不能使用带参数的QUIT退出CATCH块.全完退出用RETURN
  2. 不推荐使用无参数CATCH。
  3. 自定义异常与ISA()判断异常类型。

大纲

CATCH exceptionvar
{
   . . .
}

参数

  • exceptionvar 可选-异常变量。指定为局部变量,带或不带下标,接收对Caché Object (OREF)的引用。可以选择使用括号将此参数括起来。

描述

CATCH命令定义了一个异常处理程序,一个在TRY代码块中发生异常时要执行的代码块。Catch命令后面跟有一段代码语句,用大括号括起来。

如果指定TRY块,则需要CATCH块;每个TRY块必须具有对应的CATCH块。每个TRY块只允许一个CATCH块。CATCH块必须紧跟在其TRY块之后。TRY块和其CATCH块之间不允许有可执行代码行。TRY块与其CATCH块之间或与CATCH命令相同的行上不允许有任何标签。但是,可以在TRY块和其CATCH块之间包含注释。

当发生异常时,将输入CATCH块。如果未发生异常,则不应执行CATCH块。切勿使用GOTO语句进入CATCH块。

可以使用QUITRETURN退出CATCH块。QUIT退出当前块结构,并继续执行该块结构之外的下一个命令。例如,如果在嵌套的CATCH块中,则发出QUIT命令会将该CATCH块退出到封闭的块结构。不能使用带参数的QUIT退出CATCH块;尝试这样做会导致编译错误。 若要从CATCH块内完全退出例程,请发出RETURN语句。

CATCH命令有两种形式:

  • 无参数
  • 有参

捕获异常处理

CATCH exceptionvar从try块接收对象实例引用(OREF),在发生系统错误的情况下,该引用可以由Throw命令显式传递,也可以隐式从系统运行时环境传递。此对象提供包含有关异常的信息的属性。

一个异常可以传递四个要捕获的异常属性。它们按顺序是:名称、代码、位置和数据。引发的异常不能传递位置参数。可以使用%ISA()实例方法来确定在这些属性中传递的异常类型。

在下面的示例中,try块可以生成系统异常(未定义的局部变量)、引发SQL异常、引发状态异常或引发常规异常。此通用捕获异常处理程序确定发生哪种类型的异常,并显示相应的属性。它显示系统异常的所有四个属性(Data属性是某些类型的系统错误的空字符串)。它显示SQL异常的两个属性(代码和数据)。它向$SYSTEM.Status.Error()提供两个属性,以生成状态异常的错误消息字符串。它显示常规ObjectScript异常的三个属性(名称、代码和数据)。它使用$ZCVT函数格式化包含尖括号的名称值以供浏览器显示:

/// d ##class(PHA.TEST.Command).TestCatch()
ClassMethod TestCatch()
{
	TRY {
		SET x=$RANDOM(4)
		IF x=0 { 
			KILL undefvar
			WRITE undefvar 
		}
		ELSEIF x=1 { 
			SET oref=##class(%Exception.SQL).%New(,"-999",,"SQL error message")
			THROW oref 
		}
		ELSEIF x=2 { 
			SET oref=##class(%Exception.StatusException).%New(,"5002",,$LISTBUILD("My Status Error"))
			THROW oref 
		}
		ELSE {
			SET oref=##class(%Exception.General).%New("<MY BAD>","999",,"General error message") 
			THROW oref 
		}
		WRITE "this should not display",!
	}
	CATCH exp { 
		WRITE "In the CATCH block",!
		IF 1=exp.%IsA("%Exception.SystemException") {
				WRITE "System exception",!
				WRITE "Name: ",$ZCVT(exp.Name,"O","HTML"),!
				WRITE "Location: ",exp.Location,!
				WRITE "Code: "
		}
		ELSEIF 1=exp.%IsA("%Exception.SQL") {
			WRITE "SQL exception",!
			WRITE "SQLCODE: "
		}
		ELSEIF 1=exp.%IsA("%Exception.StatusException") {
			WRITE "Status exception",!
			DO $SYSTEM.Status.DisplayError($SYSTEM.Status.Error(exp.Code,exp.Data))
			RETURN
		}
		ELSEIF 1=exp.%IsA("%Exception.General") {
			WRITE "General ObjectScript exception",!
			WRITE "Name: ",$ZCVT(exp.Name,"O","HTML"),!
			WRITE "Code: "
		}
		ELSE { 
			WRITE "Some other type of exception",! RETURN }
			WRITE exp.Code,!
			WRITE "Data: ",exp.Data,! 
		RETURN
	}
}
DHC-APP>d ##class(PHA.TEST.Command).TestCatch()
In the CATCH block
General ObjectScript exception
Name: &lt;MY BAD&gt;
Code: 999
Data: General error message
 
DHC-APP>d ##class(PHA.TEST.Command).TestCatch()
In the CATCH block
Status exception
 
错误 #5002: Cache错误: My Status Error

DHC-APP>d ##class(PHA.TEST.Command).TestCatch()
In the CATCH block
SQL exception
SQLCODE: -999
Data: SQL error message

DHC-APP>d ##class(PHA.TEST.Command).TestCatch()
In the CATCH block
System exception
Name: &lt;UNDEFINED&gt;
Location: zTestCatch+4^PHA.TEST.Command.1
Code: 9
Data: undefvar

嵌套的TRY/CATCH

每个TRY块只允许一个CATCH块。但是,可以嵌套成对的TRY/CATCH块。

可以将内部TRY/CATCH对嵌套在外部CATCH块中,如下所示:

  TRY {
       /* TRY code */
  }
  CATCH exvar1 {
      /* CATCH code */
       TRY {
           /* nested TRY code */
       }
       CATCH exvar2 {
          /* nested CATCH code */
       }
  }

可以将内部TRY/CATCH对嵌套在外部TRY块中,如下所示:

  TRY {
       /* TRY code */
       TRY {
           /* nested TRY code */
       }
       CATCH exvar2 {
          /* nested CATCH code */
       }
  }
  CATCH exvar1 {
      /* CATCH code */
  }

执行堆栈

%Exception对象包含创建该对象时的执行堆栈。可以使用StackAsArray()方法访问此执行堆栈。下面的示例显示此执行堆栈:

/// d ##class(PHA.TEST.Command).TestCatchStack()
ClassMethod TestCatchStack()
{
	TRY {
		WRITE "In the TRY block",!
		WRITE 7/0
	}
	CATCH exobj {
		WRITE "In the CATCH block",!
		WRITE $ZCVT($ZERROR,"O","HTML"),!
		TRY {
			WRITE "In the nested TRY block",!
			KILL fred
			WRITE fred
		}
		CATCH exobj2 {
			WRITE "In the nested CATCH block",!
			WRITE $ZCVT($ZERROR,"O","HTML"),!!
			WRITE "The Execution Stack",!
			DO exobj2.StackAsArray(.stk)
			ZWRITE stk
		}
	}
}
DHC-APP>d ##class(PHA.TEST.Command).TestCatchStack()
In the TRY block
In the CATCH block
&lt;DIVIDE&gt;zTestCatchStack+3^PHA.TEST.Command.1
In the nested TRY block
In the nested CATCH block
&lt;UNDEFINED&gt;zTestCatchStack+11^PHA.TEST.Command.1 *fred
 
The Execution Stack
stk=2
stk(1)="DO"
stk(1,"PLACE")=" 0"
stk(2)=""
stk(2,"PLACE")="zTestCatchStack+11^PHA.TEST.Command.1 1"

CATCH$ZTRAP$ETRAP

不能在TRY块内设置$ZTRAP$ETRAP。但是,可以在CATCH块中设置$ZTRAP$ETRAP。还可以在进入TRY块之前设置$ZTRAP$ETRAP。如果CATCH块内发生异常,则采用指定的$ZTRAP$ETRAP异常处理程序。

TRY / CATCH循环

TRY块调用返回到TRY块的CATCH块的循环不会无限循环。它最终会发出<FRAMESTACK>错误。

禁用CATCH

发出ZBREAK /ERRORTRAP:OFF命令将禁用捕获异常处理。

参数

exceptionvar

  • 局部变量,用于在发生系统错误时从抛出命令或从系统运行时环境接收异常对象引用。

  • 发生系统错误时,exceptionvar接收对%Exception.SystemException类型的对象的引用。

  • 当发生用户指定的错误时,exceptionvar接收对%Exception.General、%Exception.StatusException或%Exception.SQL类型的对象的引用。

  • 可以选择使用括号将exceptionvar参数括起来,例如:Catch(Var){code block}。提供此括号语法是为了兼容,对功能没有影响。

示例:系统异常

下面的示例显示由被零除的运行时错误调用的无参数Catch。它显示$ZERROR$ECODE错误值。不推荐使用无参数CATCH,因为它不如传递exceptionvar可靠。如果CATCH块中发生错误,$ZERROR将包含此最新错误,而不是调用CATCH的错误。 在本例中,QUIT命令退出CATCH块,但不阻止“fall-through”到块结构之外的下一行:

/// d ##class(PHA.TEST.Command).TestCatchZERROR()
ClassMethod TestCatchZERROR()
{
	TRY {
		WRITE !,"Try块即将被零除",!!
		SET a=7/0
		WRITE !,"这不应显示"
	}
	CATCH {
		WRITE "Catch块异常处理程序",!!
		WRITE "$ZERROR is: ",$ZERROR,!
		WRITE "$ECODE is :",$ECODE,!
		QUIT
		WRITE !,"这不应显示"
	}
	WRITE !,"这就是代码失败的地方falls through"
}

DHC-APP> d ##class(PHA.TEST.Command).TestCatchZERROR()
 
Try块即将被零除
 
Catch块异常处理程序
 
$ZERROR is: <DIVIDE>zTestCatchZERROR+3^PHA.TEST.Command.1
$ECODE is :,M6,M17,M6,M9,M6,M9,M6,M9,M9,
 
这就是代码失败的地方falls through

下面的示例显示由被零除的运行时错误调用并接收参数的Catch。这是首选的编码方式。myexp OREF参数接收系统生成的异常对象。它显示此异常实例的名称、代码和位置属性。在此示例中,RETURN命令退出程序,因此不会发生“fall-through”

/// d ##class(PHA.TEST.Command).TestCatchNOZERROR()
ClassMethod TestCatchNOZERROR()
{
	TRY {
		WRITE !,"Try块即将被零除",!!
		SET a=7/0
		WRITE !,"这不应显示"
	}
	CATCH myexp {
		WRITE "Catch块异常处理程序",!!
		WRITE "Name: ",$ZCVT(myexp.Name,"O","HTML"),!
		WRITE "Code: ",myexp.Code,!
		WRITE "Location: ",myexp.Location,!
		RETURN
	}
	WRITE !,"这就是代码失败的地方 falls through"
}
DHC-APP>d ##class(PHA.TEST.Command).TestCatchNOZERROR()
 
Try块即将被零除
 
Catch块异常处理程序
 
Name: &lt;DIVIDE&gt;
Code: 18
Location: zTestCatchNOZERROR+3^PHA.TEST.Command.1
 

下面的示例显示了一个接收系统异常对象的CATCHCATCH块代码使用%Exception.SystemException类的AsSystemError()方法将系统异常显示为$ZERROR格式的字符串。(为了便于比较,还会显示$ZERROR。)然后,此CATCH块将错误名称、错误代码、错误数据和错误位置显示为单独的属性:

/// d ##class(PHA.TEST.Command).TestCatchException()
ClassMethod TestCatchException()
{
	TRY {
		WRITE !,"此global未定义",!
		SET a = ^badglobal(1)
		WRITE !,"这不应显示"
	}
	CATCH myvar {
		WRITE !,"这是异常处理程序",!
		WRITE "AsSystemError is: ",myvar.AsSystemError(),!
		WRITE "$ZERROR is:       ",$ZERROR,!!
		WRITE "Error name=",$ZCVT(myvar.Name,"O","HTML"),!
		WRITE "Error code=",myvar.Code,!
		WRITE "Error data=",myvar.Data,!
		WRITE "Error location=",myvar.Location,!
		RETURN
	}
}

DHC-APP>d ##class(PHA.TEST.Command).TestCatchException()
 
此global未定义
 
这是异常处理程序
AsSystemError is: <UNDEFINED>zTestCatchException+3^PHA.TEST.Command.1 ^badglobal(1)
$ZERROR is:       <UNDEFINED>zTestCatchException+3^PHA.TEST.Command.1 ^badglobal(1)
 
Error name=&lt;UNDEFINED&gt;
Error code=93
Error data=^badglobal(1)
Error location=zTestCatchException+3^PHA.TEST.Command.1
 

示例:抛出的异常

下面的示例显示了由Throw命令调用的Catchmyvar参数接收具有四个属性的用户定义的异常对象。请注意,在此示例中,抛出不为%Exception.General类的省略Location属性提供值:

/// d ##class(PHA.TEST.Command).TestCatchThrow()
ClassMethod TestCatchThrow()
{
	TRY {
		SET total=1234
		WRITE !,"Throw an exception引发异常!"
		THROW ##class(%Exception.General).%New("Example Error",999,,"MyThrow")
		WRITE !,"这不应显示"
	}
	CATCH myvar {
		WRITE !!,"这是异常处理程序"
		WRITE !,"Error data=",myvar.Data
		WRITE !,"Error code=",myvar.Code
		WRITE !,"Error name=",myvar.Name
		WRITE !,"Error location=",myvar.Location,!
		RETURN
	}
}
DHC-APP>d ##class(PHA.TEST.Command).TestCatchThrow()
 
Throw an exception引发异常!
 
这是异常处理程序
Error data=MyThrow
Error code=999
Error name=Example Error
Error location=

以下两个示例在try块中生成出生日期。如果它们生成将来的出生日期,则使用Throw发出一般异常,并将用户定义的异常传递给Catch块。(可能需要多次运行这些示例才能生成抛出异常的日期。)

这些示例中的第一个没有指定Catch exceptionvar。它使用TRY块中定义的OREF名称指定异常属性:

/// d ##class(PHA.TEST.Command).TestCatchThrowFirst()
ClassMethod TestCatchThrowFirst()
{
	TRY {
		WRITE "在TRY块中",!
		SET badDOB=##class(%Exception.General).%New("BadDOB","999",,"Birth date is in the future")
		FOR x=1:1:20 { 
			SET rndDOB = $RANDOM(7)_$RANDOM(10000)
			IF rndDOB > $HOROLOG { 
				THROW badDOB 
			}
			ELSE { 
				WRITE "Birthdate ",$ZDATE(rndDOB,1,,4)," is valid",! 
			}
		}
	}
	CATCH {
		WRITE !,"在CATCH块中"
		WRITE !,"Birthdate ",$ZDATE(rndDOB,1,,4)," is invalid"
		WRITE !,"Error code=",badDOB.Code
		WRITE !,"Error name=",badDOB.Name
		WRITE !,"Error data=",badDOB.Data
		RETURN
	}
}
DHC-APP>d ##class(PHA.TEST.Command).TestCatchThrowFirst()
在TRY块中
Birthdate 01/15/1877 is valid
Birthdate 01/12/1998 is valid
Birthdate 02/08/1958 is valid
 
在CATCH块中
Birthdate 12/12/2023 is invalid
Error code=999
Error name=BadDOB
Error data=Birth date is in the future

这些示例中的第二个指定了Catch exceptionvar。它使用这个重命名的OREF来指定异常属性。这是首选用法:

/// d ##class(PHA.TEST.Command).TestCatchThrowSecond()
ClassMethod TestCatchThrowSecond()
{
	TRY {
		WRITE "在TRY块中",!
		SET badDOB=##class(%Exception.General).%New("BadDOB","999",,"Birth date is in the future")
		FOR x=1:1:20 { 
			SET rndDOB = $RANDOM(7)_$RANDOM(10000)
			IF rndDOB > $HOROLOG { 
				THROW badDOB 
			}
			ELSE { 
				WRITE "Birthdate ",$ZDATE(rndDOB,1,,4)," is valid",! 
			}
		}
	}
	CATCH err {
		WRITE !,"在CATCH块中"
		WRITE !,"Birthdate ",$ZDATE(rndDOB,1,,4)," is invalid"
		WRITE !,"Error code=",err.Code
		WRITE !,"Error name=",err.Name
		WRITE !,"Error data=",err.Data
		RETURN
	}
}
DHC-APP>d ##class(PHA.TEST.Command).TestCatchThrowSecond()
在TRY块中
Birthdate 03/15/2012 is valid
 
在CATCH块中
Birthdate 05/20/2032 is invalid
Error code=999
Error name=BadDOB
Error data=Birth date is in the future

示例:嵌套的TRY/CATCH

下面的示例显示由被零除的运行时错误调用的CatchCATCH块包含与内部CATCH块成对的内部TRY块。此内部CATCH块由引发的异常调用。出于演示的目的,在此程序中随机调用此THROW。在实际程序中,内部CATCH块将由异常测试调用,例如AsSystemError()(捕获的错误)和$ZERROR(最新错误)之间的不匹配:

/// d ##class(PHA.TEST.Command).TestCatchThrowNested()
ClassMethod TestCatchThrowNested()
{
	TRY {
		WRITE !,"外部TRY块",!!
		SET a=7/0
		WRITE !,"这不应显示"
	}
	CATCH myexp {
		WRITE "外部CATCH块",!
		WRITE "Name: ",$ZCVT(myexp.Name,"O","HTML"),!
		WRITE "Code: ",myexp.Code,!
		WRITE "Location: ",myexp.Location,!
		SET rndm=$RANDOM(2)
		IF rndm=1 {
			RETURN 
		}
		TRY {
			WRITE !,"内部TRY块",!
			SET oref=##class(%Exception.General).%New("<MY BAD>","999",,"一般错误消息") 
			THROW oref
			RETURN
		}
		CATCH myexp2 {
			WRITE !,"内部CATCH块",!
			IF 1=myexp2.%IsA("%Exception.General") {
				WRITE "常规ObjectScript异常",!
				WRITE "Name: ",$ZCVT(myexp2.Name,"O","HTML"),!
				WRITE "Code: ",myexp2.Code,!
			}
			ELSE { 
				WRITE "一些其他类型的异常",! 
			}
			QUIT
		}
		WRITE !,"返回到外部CATCH块",!
		RETURN
	}
}
DHC-APP>d ##class(PHA.TEST.Command).TestCatchThrowNested()
 
外部TRY块
 
外部CATCH块
Name: &lt;DIVIDE&gt;
Code: 18
Location: zTestCatchThrowNested+3^PHA.TEST.Command.1
 
内部TRY块
 
内部CATCH块
常规ObjectScript异常
Name: &lt;MY BAD&gt;
Code: 999
 
返回到外部CATCH块