Wiki Home

Try Catch


Namespace: DotNet
TRY...CATCH...FINALLY...ENDTRY is new in VFP 8

Try / Catch / Finally = structured error handling. See also VFPStructured Error Handling MatrixOffsite link to http://www.cycla.com/software/vfperrorhandlingmatrix.htm

An article on Structured Error Handling. This is geared to .Net, but most of the concepts are the same:
http://msdn.microsoft.com/msdnmag/issues/02/11/NETExceptions/default.aspx

You can't THROW out of a DLL, but you can let the Error method trap the exception and call COMRETURNERROR.

NOTE - As of VFP 9 (beta), your Exception object cannot be a property of an object (oMyObject.oError) nor can you use the "m.exception" notation. They will both trigger a run-time error. -- Paul James

Here's my example of embedding a Begin...End Transaction within a Try...Catch. -- Paul James
   Local oError as Exception
   Try
      wait window "Applying changes..." nowait noclear
      Begin Transaction
         Select MyParentAlias
         replace par_pk with liNewPK

         Select MyChildAlias
         replace all chi_par_pk with par_pk where chi_par_pk = liOldPK
      End Transaction
   Catch to oError When .T.
      wait window "Error, rolling back changes..." nowait noclear
      RollBack
      =messagebox("Error Updating Tables!"+chr(13)+;
                  oError.Message+chr(13)+;
                  "Error #:"+Transform(oError.ErrorNo)+chr(13)+;
                  "Line #:"+Transform(oError.LineNo)+chr(13)+;
                  "Error #:"+Transform(oError.LineContents),48,"Error")
   Finally
      Wait clear
   EndTry

   If type('oError') = 'O' and !isnull(oError)
      Return oError.ErrorNo
   Endif
   Return 0

It's powerful - here's how it works in its most basic form:
TRY

  TRY
     ...code
     ....code ... causes error... same as THROW
     .....code .. this doesn't execute
  FINALLY
    ... This ALWAYS executes
  ENDTRY
  ...code .. this doesn't execute because our error was not handled in the
  ...        above TRY..ENDTRY block... execution passes to the next higher CATCH

CATCH [TO varname] [WHEN lexpression] (optional)
  ...error occurred in any of the code above come into here and do something
  ... ok.. I know there was an error, but I don't know what do do with it, so...
  THROW varname
FINALLY  (optional)
  ... this code will execute whether errors between TRY and CATCH are
  ...  'Caught' or not.... and even though the CATCH was left via THROW
ENDTRY

added colors for readability - I think
Call me dense, but what's the point of the FINALLY if it executes regardless...?? - William Fields
Duh... beats me! Can someone answer this? -- Peter Crabtree
Sure... If there is an error, but it's not Caught, then FINALLY code DOES execute, and at the ENDTRY, execution goes directly to the next higher CATCH, whether it is in the same routine or in a calling routine. -- ?wgcs
No, this is wrong. Code in the FINALLY section executes all the time, exception or not (except in rare cases, like COM error). This give you the ability to do some cleanup before exiting the function. -- SL
Sorry if I was misleading: I was trying to point out, given that William Fields said "what's the point if it executes regardless?" that the point is that it DOES execute even when there is an exception. Any code after the ENDTRY DOES NOT execute if there is an exception or error. -- ?wgcs
I don't think so, wgcs. That would essentially mean that, if you coded a TRY with the intent to deal with an exception and CONTINUE regular processing, then ENDTRY would have to be the last line of code in the program. -- Jim Nelson
Ok, One more clarification... I was hasty in typing: I'm talking about the case of an Unhandled exception. As in when THROW is called in the CATCH section, or when there IS NO CATCH section. The FINALLY code will still execute, but any code after the ENDTRY will not... execution will pass straight up the CallStack to the next higher TRY...CATCH structure. -- ?wgcs

I believe the theory is that whether the code succeeds or fails, you want to be able to clean up any object references or close any forms or work areas that are no longer needed when the TRY block is no longer executing. -- Pamela Thalacker
OK, from the comments and code example above the light may be coming on, tell me if I have this right - When a THROW is issued, an error is thrown and code execution in the current method/procedure may be canceled (depending on the error handler) and control is then returned to the calling method/procedure on the call stack, that is except for the code in the FINALLY section. If that sounds right, please feel free to put it into a better explanation - William Fields
Once again, I have not done much experimenting, but my understanding is that when an error occurs in a TRY block, no further code is executed in that block beyond the error. If the error isn't handled by a CATCH in that TRY block, then VFP runs the code in the FINALLY block and clears out code in the call stack until it finds something to handle the error. I am still a little fuzzy myself about what qualifies to handle the error on the way up the stack. I believe it is only other CATCH statements until it hits the VFP error handler, but I am not sure. -- Pamela Thalacker
Ok, I think I get it. Something like this:

TRY
  TRY
    PUBLIC gnFileHandle
    gnFileHandle = FOPEN("myfile.txt")
    * Some other code that errors here
  CATCH
    THROW && This will go to the higher-level CATCH
    *OR, if no CATCH was here, then it would just immediately go to the higher-level CATCH, after the FINALLY
  FINALLY
    IF VARTYPE(gnFileHandle) == "N" AND gnFileHandle > 0
      FCLOSE(gnFileHandle)
    ENDIF
  ENDTRY
  *Some other code here, which may or may not ever run
CATCH
  MESSAGEBOX("Error doing function")
ENDTRY


Now, where else could you put the gnFileHandle FCLOSE(), which would keep it immediately cleaning up the file referance, and will *always* run?
-- Peter Crabtree


The above takes precedence over any global ON ERROR. Errors are automatically written to an Exception object. I see 2 immediate benefits:

1. Reduces code! Replace the following:
on error lcheck=.t.
use xyz
IF lcheck
   do something error related
ELSE
   oWord = CreateObject("word.application")
   IF vartype(oWord) <> "O"
      * do something error related
      RETURN .F.
   ELSE
      oDoc = oWord.Documents.Open("MyDocument.doc")
      IF vartype(oDoc) <> "O"
         * document did not open
         RETURN .F.
      ENDIF
      oWord.visible = .t.
  ENDIF
ENDIF

With:
TRY
    use xyz

    * this way, the same CATCH will handle errors
    * on instancing Word, or on attempting to open
    * a Word document
    oWord = CreateObject("word.application")
    oDoc = oWord.Documents.Open("MyDocument.doc")
    oWord.visible=.t.
CATCH
   ...handle error here
ENDTRY


2. For me at least - currently I use a global ON ERROR() that calls a routine to write the error particulars to a table and exit the user from the app. Any error in my app will exit the user.

Try / Catch would allow wrapping error prone areas and handle without completely exiting the app - like the Word instantiation above - that can fail for reasons outside the app, etc. -- Peter Diotte

Correct. I added code to open a document in your Word example. This way you see more related code that is error-prone (instancing Word, then loading a document), that is wrappeed inside an error handler. -- Alex Feldstein
Also, Try...Catch has nesting capabilities that would be difficult or impossible to implement with on error. -- Ray Kirk
Note that FINALLY runs every time, within the TRY/CATCH statement. -- Alex Feldstein
(Unless you issue COMRETURNERROR in the catch section) -- Stuart Dunkeld
Something to consider when thinking of TRY/CATCH is that you can "throw" your own exceptions (that are not neccessary errors) and still catch them with TRY/CATCH. For example: -- Hector Correa
TRY
   ? "try"
   * [some code here...]
   if .T.
      * we've decided that we don't want
      * to proceed any further.
      throw 999
   endif
   * [...more code here]
CATCH TO oEx WHEN oEx.UserValue=999
   ? "caught our own exception 999"
CATCH TO oEx
   ? "catch statement for everything else"
FINALLY
   ? "finally"
ENDTRY

Ok, everybody: One... Two... Three... "Thank you Redmond!"
I totally agree. Just when you think 7.0 is the cat's meow, the sharp and dedicated Fox team at MSFT blow the doors off with a feature rich, solid and even faster version 8.0. 8.0 is another must have.
Yesss! Seems that our beloved VFP team has narrowed the gap -in error handling an some other stuff- between VFP and other well reputed languages such Borland's Delphi. IMHO, I think, like a noble wine, VFP gets better with the time. Kudos for VFP team at Redmond!
I really like the new TRY/CATCH. I would like however that MS mark ON ERROR and the Error method in a class as obsolete. I just imagine the nighmare debugging codes that mix the 3 styles on error handling... -- SL

Actually, they work pretty well togther. But, you can use Structured Error handling exclusivly. But if you do mix them you can think of...

ON ERROR as the global level error handler
Error() method as the class level error handler
Try...EndTry as the method level error handler

The only weird catch to this, that I see, is, if you call a method of a class inside the try block, and an error occurs, VFP is going to run the code in the error method of that class first. If there is no error method code, then it will go to the catch of the current try.

What I'm trying to figure out is why would you want to use Error() or ON ERROR. Class level error handling is an artifact of the old way, and TRY...CATCH works better as a global error handler than ON ERROR does. -- Mike Helland
No, it doesn't (yet?) -
1) We (our "framework") log all errors in a table with all infos we can get.
If you have code like this
TRY && for global error handling
  DO FORM myForm
CATCH TO o_Err
  && save all o_Err Properties in error-table
ENDTRY

and an error happens in a click-event of a button you get (in o_Err.Procedure) "click" :-(

ON ERROR myGlobalErrorHandler(PROGRAM())
will give you "frmMyForm.MyNastyButton.Click" which is ... better.

2) From our global error-handling function:
  if debugmode
    set step on
    return
  endif

which makes debugging easier. I can go back to my code, can click "fix" in the debug-menu or can change a value of a variable and continue ... with TRY-CATCH it's too late - you're back in your main.prg

3) RETRY (which isn't supported by TRY-CATCH)

4) (from the help) "Visual FoxPro supports Set Next Statement debugging functionality only within a single code block." - another drawback!

So TRY-CATCH-ENDTRY is for me as global errorhandling not acceptable because debugging is with ON ERROR easier.

I'd support: ON ERROR for global error-handling and TRY...CATCH "as local as possible". Even in a new project!
--Kurt

Well, if you are starting a new project, I would go 100% structured. But, for an existing project that uses Global and Error() method you can still make use of some method level try...endtry blocks. Then again, it would be pretty easy to pull the ON ERROR out of your main and wrap you read events with a TRY...ENDTRY structure.

The issue is that if you are trying to implement structured error handling, and for example, you have a business object that needs to throw up an exception to the client code's CATCH, having an active Error() event in the class will mess up that interaction between the objects. Hmmm, thats what I assumed anyways. Can someone with the beta confirm this? I'll check when I get home. -- Mike Helland

Hmmm... Yes, for new applications, a TRY as the first line of the main program, and CATCH...ENDCATCH at the end emulates ON...ERROR plus benefits (THROW anywhere, etc). However, existing applications can still benefit, without removing ON...ERROR. Also, implementing the TRY...ENDTRY to wrap everything will break code which uses ON...ERROR to trap local errors! So while a new application could use TRY...CATCH exclusively (perhaps there would still be a use for Error()), but you have to go through and make sure it won't break anything on an existing app. P.S. (not exactly on-topic, but) I love VFP8! I dreamed about TRY...CATCH and BindEvents() last night :) Yes, but have you ever tried to use the Properties Window to turn off your alarm clock? :-)
That could be interesting alright, BUT I'd want to know a bit more about overhead cost and review real-life experiences (read: SURPRISES) before going whole-hog with it. -- Jim Nelson

There could be a problem when using Try/Catch as global error handling (to replace a global On error code). If any error occurs in the program, the application will execute Catch statememts, then Finally section and then exit. There is no way to recover from error, as it it possible with On error global procedure - Bogdan Zamfir
Who says that "QUIT" must be right after the the ENDTRY? Consider this:
PROCEDURE MainProgram
TRY
  LOCAL llInitComplete,llKeepRunning
  llInitComplete = .F.
  llKeepRunning  = .T.
  DO WHILE llKeepRunning
    * The "Exit" choice on the menu should
    *   set llKeepRunning=.F.
    *   then CLEAR EVENTS

    TRY
      if not llInitComplete
        * set up global state
        * Set up the menues
        * create the main form
        llInitComplete = .T.
      ELSE
        * Check to make sure the main menu still "looks good"
        * (so the user can still access the application features)
      ENDIF

      * Event handler:
      READ EVENTS

    CATCH TO oExc
      DO CASE
        CASE INLIST(oExc.ErrorNo,1,2,3) && etc
          * Handle the error
          * Log it, notify the user? Clean up? (the stack automatically cleaned up)
        CASE INLIST(oExc.ErrorNo,1,2,3) && etc
          * Fatal errors (table corruption, etc)
          MESSAGEBOX('A fatal error has occurred.  You must remedy the cause of the error before restarting')
          llKeepRunning = .F.
        OTHERWISE
          * We don't know what might have been damaged
          MESSAGEBOX('An unrecognized error has occurred. Cannot keep running!')
          llKeepRunning = .F.
      ENDCASE
    FINALLY
      * Clean up anything that must be cleaned up before looping again.
    ENDTRY

  ENDDO
CATCH TO oExc
  ?"Ooops: Major problem in main execution loop!"
ENDTRY
QUIT  && I put this here to make clear that when you get here, you quit!
ENDPROC
-- wgcs
Using ON ERROR, you can use PROGRAM() or ASTACKINFO() to give you the stack trace at the time of the error. If you do the same thing in the CATCH block, then you get the current stack trace, any ideas on how to get the stack trace at the time of the error? -- Tom

Like I wrote above, I use ON ERROR for global error-handling and TRY...CATCH "as local as possible". Debugging can be very uncomfortable with TRY...CATCH (Problem with set next statement, no RETRY, no stackinfo, sometimes incomplete info where the error occured ...). Maybe in future Versions of VFP this will be better, but for now I use TRY ... CATCH only for small code-blocks. Why should one not use ON ERROR? Because a Java-programmer could say "oh, this is so unOOP, it's so unfashioned"? I don't care. I don't want to buy the drawbacks of the current TRY...CATCH-implementation in VFP by using TRY...CATCH everywhere and kicking out ON ERROR completely without having any advantages. my 0.02 -- Kurt

The code in the TRY/CATCH that I want to be able to debug (and hence need good stack info etc.) is script code in a HTML template. The TRY/CATCH allows me to exit cleanly and close files that otherwise would require restarting the web-server. It may not be "as local as possible" but it's made it a lot more stable, just at the expense of a little debugging info :(

Ideally, the ON ERROR would be within the TRY/CATCH, with the error handler getting the stack info and then THROWing. -- Tom

Ideally, TRY/CATCH would give us everything what ON ERROR gives us :-) ON ERROR within TRY/CATCH would possibly break my code :-( My understanding was that the ON ERROR will be ignored within a TRY/CATCH block -- Tom
Yes, I would like to use TRY/CATCH in this case, too! But for now, I would wait until MS improved TRY/CATCH and use a (not so beautiful) work-around: Doing it old-fashioned

lcError = ON("Error")
ON ERROR myScriptInHtmlTemplateErrorHandler (...) && ;-)
&& do the Html-Stuff
ON ERROR &lcError


Maybe the Error-Event is an option. What about an HtmlTemplateScripting-Class with such an Error-Event? -- Kurt

The big advantage of TRY/CATCH over ON ERROR and the Error event is that it "abandons" the execution of any further code (that would obviously be compromised due to the error.) This lets me carry on returning a sensible response to the web server. Using ON ERROR I had to use Com Return Error() to "kill" the execution, but this left some files open (.FXP files for example) which then required a restart of the web server to unload. -- Tom

I'm not sure if I understand you right (English is not my mother tongue). I understand you want
  TRY
    * Scripting stuff
    * other code
  CATCH TO oErr && an Error in the HTML-sript
    * log StackInfo/ErrorInfo from where the error occured (perhaps a function call in a script)
    * prepare errormsg as response to webserver
  FINALLY
    * clean up
    * response to webserver
  ENDTRY

This would be clean, nice and easy - but doesn't work for you because of the missing stackinfo.

You're right, that's more or less what I have now.

Idea:
WITH CREATEOBJECT("theScriptingWrapper")
  .doScripting()
  IF .ok
    * other Code
  ENDIF
  * clean up
  * response to webserver
ENDWITH

DEFINE CLASS theScriptingWrapper AS CUSTOM

  ok = .T.

  PROCEDURE doScripting
    * ScriptingStuff
  ENDPROC

  PROCEDURE Error
  LPARAMETERS nError, cMethod, nLine
    * log StackInfo
    * prepare errormsg as response to webserver
    this.ok = .F.
  ENDPROC
ENDDEFINE

It's not clean, less nice and a more complex - maybe it works for you. -- Kurt
(we should refactor this part of this wikipage later)

That looks fine, and I actually already have a class doing the same job as theScriptingWrapper so would only have to add the Error event code.

I was just experimenting a little, and the main problem is that control returns to the doScripting method after the Error event fires. Using the following modified version of your code works though:

WITH CREATEOBJECT("theScriptingWrapper")
  .doScripting()
  IF .ok
    * other Code
    ? "C"
  ENDIF
  * clean up
  * response to webserver
  ? "D"
ENDWITH

DEFINE CLASS theScriptingWrapper AS CUSTOM
  ok = .T.

  PROCEDURE doScripting
    this.doScripting2
  ENDPROC

  PROCEDURE doScripting2
    * ScriptingStuff
    ? "A"
    ? generate an error here
    ? "B"
  ENDPROC

  PROCEDURE Error
  LPARAMETERS nError, cMethod, nLine
    * log StackInfo
    * prepare errormsg as response to webserver
    this.ok = .F.
    RETURN TO doScripting
  ENDPROC
ENDDEFINE


When run, this outputs "A","D", which is the same result as TRY/CATCH plus I can get the stack info! If there was no error, you would expect it to output "A","B","C","D".

I'd never triedRETURN [TO FunctionName] before, you learn something about FoxPro every day!

-)
You really can return to doscripting? When I tried it it didn't work (I started to write a test-framework some time ago)

Thanks for you're help Kurt, next step, refactoring this page! -- Tom

Update: When I tried to put this into practice it failed... If I wrap a TRY/CATCH around the WITH/ENDWITH above, you get a "RETURN/RETRY statement not allowed in TRY/CATCH" error, so there goes that idea.

What looks like it should work is to grab the stack info in the Error event and THROW it (then it will be in Exception.UserValue), when I tried this though, the exception doesn't get caught by the TRY/CATCH and gives a "User Thrown Error" C/S/I message. So close! -- Tom

It's a pity. According to my "theory": This TRY/CATCH is too global :-(.
I added a wish to the wish-list at www.universalthread.com:
New _Vfp-Properties ExceptionClass and ExceptionClassLibrary, like MemberClass/MemberClassLibrary in Grid, Pageframe ... so one can make his own exception-class that can get stackinfo in its init-event.
Maybe you can comment this.
-- Kurt

Alternatively, a StackInfo property could be added to the base Exception class. Adding an ExceptionClass property is a better idea though, as it allows for future functionality to be added in.

In the short term, I think I'll just remove the outer TRY/CATCH, as I think the RETURN TO gives me the control I need. -- Tom

If a potential error is not handled in a Catch block (either because there isn't a matching Catch-block or because another exception is THROWn), code after the Try/Catch statements may not be executed at all. Consider this example:
TRY
    xxxxxx && <<<--- error
CATCH TO oEx
    MESSAGEBOX("Error!")
    THROW oEx
FINALLY
    MESSAGEBOX("Cleanup Code")
ENDTRY
MESSAGEBOX("More Code") && <<<--- oops

Here is a bug that I couldn't find documented anywhere:

This is a Visual FoxPro bug about
FoxPro Version: 9.0 No SP
Reproduces: always

Steps to Reproduce:
(1) Open VFP
(2) Create a procedure file "TestOexc.prg" containing the following code:
TRY
  TRY
    THROW "Expected 1"
  CATCH TO oExc
    MESSAGEBOX('TestOexc Caught '+oExc.Message+'  '+TRANSFORM(oExc.UserValue) )
    cMsg = oExc.message
    DO ErrProc WITH cMsg
  ENDTRY
CATCH TO oExc
  MESSAGEBOX('NOT EXPECTED: TestOexc Outer TRY Caught '+oExc.Message+'  '+TRANSFORM(oExc.UserValue) )
ENDTRY

PROCEDURE ErrProc( cMsg )
PRIVATE oExc
  TRY
    THROW "Expected 2"
  CATCH TO oExc
    MESSAGEBOX('Errproc Caught '+oExc.Message+'  '+TRANSFORM(oExc.UserValue) )
  ENDTRY
ENDPROC


(3) Run the procedure

Observed Behavior:
First, a dialog saying "TestOexc Caught User Thrown Error . Expected 1"
Second, a dialog saying "NOT EXPECTED: TestOexc Outer TRY Caught Alias 'OEXC' is not found."

Expected Behavior:
First, a dialog saying "TestOexc Caught User Thrown Error . Expected 1"
Second, a dialog saying "ErrProc Caught User Thrown Error . Expected 2"


Additional Information:
Even though ErrProc has the statement "PRIVATE oExc", the "TRY..CATCH TO oExc" phrase that should be creating a new, private oExc variable instead seems to be accessing the hidden, original oExc variable.

If the "PRIVATE oExc" statement is removed, it works correctly (though, I expect that then that the private oExc variable that already exists gets changed, when I'd prefer it not be changed.)

If the "PRIVATE oExc" statement is changed to "LOCAL oExc", then it all works correctly. (though, then, I expect that oExc behaves as a Local, and won't be visible in called functions like a Private var would)

- wgcs

The problem seems to be really in impossibility of "CATCH TO oExc" statement to create new variable (you know that PRIVATE doesn't really create new variable, it just "hide" existing one) - so if you'll help it a little (say with "oExc = .F." just after "PRIVATE oExc" statement), it will work as expected - "CATCH TO oExc" will redefine existing private variable.
-- Igor Korolyov
*// Start Code Here This worked for me.
try
Append From "&sImpFileName." Type CSV ;
  FOR NOT EMPTY(CaseID)
CATCH
  ON ERROR  && Reset error message..
  MESSAGEBOX("It does not appear there is a file called &sImpfilename.",0,"Please check the file name and try again.")
  lCancel = .t.
ENDTRY

IF lCancel
  RETURN .t.
ENDIF
-- Kip Dole
Category Needs Refactoring
Category VFP Commands Category VFP 8 Category VFP 8 New Features Category Error Handling
( Topic last updated: 2008.03.06 11:07:27 AM )