I wrote some examples to highlight what my issue in understanding is.
1. Example: This works as expected. The error is getting handled via the Goto Statement and the error handling is returned to normal behavior arfterwards
Sub ErrorHandlingWithoutLoop()
Debug.Print "Before Error"
On Error GoTo errorHandler
Error (1)
' If no Error occured, the ErrorHandling procedure is skipped
GoTo skip
errorHandler:
Debug.Print "Handle Error"
On Error GoTo 0
skip:
Debug.Print "After Error"
'Raise Error to see if error Handling is resumed to normal behavior
Error (14)
End Sub
VBA returns:
Before Error
Handle Error
After Error
And the Error Handling is returned to normal, visible by the thrown error (14)
Example 2: I would expect this sub to run through just fine and just throw an error in the last line with error (14).
However in the second loop with n=2, the error handling doesnt work as intended. Why?
Sub ErrorHandlingWithLoop()
Debug.Print "Before Error"
For n = 1 To 10
On Error GoTo errorHandler
Error (1)
' If no Error occured, the ErrorHandling procedure is skipped
GoTo skip
errorHandler:
Debug.Print "Handle Error", n
On Error GoTo 0
skip:
Debug.Print "After Error", n
Next n
'Raise Error to see if error Handling is resumed to normal behavior
' -> This wont be reached though
Error (14)
End Sub
VBA returns:
Before Error
Handle Error 1
After Error 1
The execution of the code stops in the second loop with n=2 with a thrown error of 1. The Error 14 at the end is not thrown
Example 3: I know I can fix this behaviour by adding a "resume skip" statement. However I dont understand why this is necessary?
Sub ErrorHandlingWithLoopFixed()
Debug.Print "Before Error"
For n = 1 To 10
On Error GoTo errorHandler
Error (1)
' If no Error occured, the ErrorHandling procedure is skipped
GoTo skip
errorHandler:
Debug.Print "Handle Error", n
On Error GoTo 0
Resume skip
skip:
Debug.Print "After Error", n
Next n
'Raise Error to confirm that error Handling is resumed to normal behavior
Error (14)
End Sub
VBA returns:
Before Error
Handle Error 1
After Error 1
Handle Error 2
After Error 2
Handle Error 3
After Error 3
Handle Error 4
After Error 4
Handle Error 5
After Error 5
Handle Error 6
After Error 6
Handle Error 7
After Error 7
Handle Error 8
After Error 8
Handle Error 9
After Error 9
Handle Error 10
After Error 10
And the Error Handling is returned to normal, visible by the thrown error (14)
So why is the Resume skip necessary?!
Thanks for any help=)
Edit:
!Bonus Example!3: Interestingly, if I add a On Error Goto -1, then the for-loop loops infinitely. Why is that? I cant make a rhyme of that...
Sub ErrorHandlingWithLoop()
Debug.Print "Before Error"
For n = 1 To 10
On Error GoTo errorHandler
Error (1)
' If no Error occured, the ErrorHandling procedure is skipped
GoTo skip
errorHandler:
Debug.Print "Handle Error", n
On Error GoTo -1
skip:
Debug.Print "After Error", n
Next n
'Raise Error to see if error Handling is resumed to normal behavior
' -> This wont be reached though
Error (14)
End Sub
VBA returns:
Before Error
Handle Error 1
After Error 1
Handle Error 2
After Error 2
Handle Error 3
After Error 3
Handle Error 4
After Error 4
Handle Error 5
After Error 5
Handle Error 6
After Error 6
Handle Error 7
After Error 7
Handle Error 8
...
...
...
This continues on and on until Excel crashes.
Any help on this as well?
Personally, I would not follow the example you provide to deal with error handling. Instead I would encapsulate the action that might result in an error in its own function which returns true or false depending on whether or not the action was successful or not. The value returned by the action is passed out of the function using a ByRef parameter.
Sub ErrorHandlingWithLoopFixed()
Debug.Print "Before Error"
Dim myResult as Variant
For n = 1 To 10
If Not TryAction( myResult) then
Debug.Print "An error occurred"
Stop ' or exit function or whatever
end if
' Continue processing myResult
Next n
'Raise Error to confirm that error Handling is resumed to normal behavior
Error (14)
End Sub
Public Function TryAction( ByRef opResult) as Boolean
On Error Resume Next
opResult = ' the action
TryAction = err.number = 0
On Error Goto 0
Exit Function
As you can see from above, the code is much easier to follow and there are no problems with jumping in and out of the scope of a For loop.
For example 2: the reason you need Resume skip is that an error handler only "handles" once and gets deactivated. After that you must activate it again which is exactly what resume does.
Paul Kelly names this: "reseting the mouse trap", because like a mouse trap an error handler gets deactivated when used, and you need to activate it manually for it to work again.
All these can perform that task (activate the mouse trap): resume label, resume next, on error goto -1. Also On Error Resume Next takes care of the reactivation itself (you don't need to do it manually for that one).
For the bonus the reason for the infinite loop is that you have an error right befor the end, but no Exit Sub before it. So whenever you reach that error you'll be thrown back in the error handler, which you've put in the loop. A safer way is to have your error handlers at the end, after an exit sub.
Related
I am stuck on something that I have a workaround for, but it bugs me I don't have a direct answer for how to address the issue of using On Error Goto for recurring errors. My question is essentially the same as this one, however the answers provided are alternatives to the OP's overall approach rather than how to handle the specific issue.
I've simplified an example below. I am WELL AWARE that there are probably a dozen ways this code could be rewritten to avoid any error -- I am just using this to illustrate the issue of trying to figure out why/how does this code work properly when i = 0, but not when i = 3,6,9?
Sub vbaErrorConfusion()
Dim theArray(9) As Long, i As Long
For i = 0 To 9
On Error GoTo fixingErrors
'next line will intentionally create an error when
'when i = {0} this works as expected, but at i=3 next line throws an error.
theArray(i) = i + 1 / (i Mod 3)
On Error GoTo 0
next_i:
Next i
Exit Sub
'----Error Handling----
'this works as expected when i=0 but not when i = 3,6,9
fixingErrors:
Err.Clear
On Error GoTo 0
'at this point I would expect my error state to be the same as the start of procedure?
theArray(i) = -1
GoTo next_i
End Sub
What my Err variable shows after first instance of 0.
After I run Err.Clear I would expect the behavior for i=3 to be the same as when i=0, however my procedure stops with the below VBA error one would expect WITHOUT any error catching.
I presume there's some way to reset the Error variable or handle this type of situation without a workaround? Any quality responses will get upvoted. Thanks.
To tell VBA that an you have dealt with the error you need a Resume statement. Therefore, replacing the line GoTo next_i with Resume next_i you give you the outcome you expect.
You do not need Err.Clear. Also, On Error GoTo 0 will disable the error handler. However, neither of these lines will tell VBA that you have dealt with an error.
In your code at i=0 the error handler is activated but VBA is not told that the error has been dealt with (i.e. no Resume statement). At i=3 another error occurs while a previous error hadn't been dealt with. In this case the current procedure/function/property cannot deal with the second error, which is therefore, fatal.
You should also take the On Error GoTo fixingErrors line outside the loop.
Sub vbaErrorConfusion()
On Error GoTo fixingErrors
Dim theArray(9) As Long, i As Long
For i = 0 To 9
'*On Error GoTo fixingErrors
'next line will intentionally create an error when
'when i = {0} this works as expected, but at i=3 next line throws an error.
theArray(i) = i + 1 / (i Mod 3)
'*On Error GoTo 0
next_i:
Next i
Exit Sub
'----Error Handling----
'this works as expected when i=0 but not when i = 3,6,9
fixingErrors:
'*Err.Clear
'*On Error GoTo 0
'at this point I would expect my error state to be the same as the start of procedure?
theArray(i) = -1
Resume next_i
End Sub
Please take some time to read up on how Error handling in VBA works.
Sub vbaErrorConfusion()
Dim theArray(9) As Long, i As Long
For i = 0 To 9
On Error GoTo fixingErrors
'next line will intentionally create an error when
'when i = {0} this works as expected, but at i=3 next line throws an error.
theArray(i) = i + 1 / (i Mod 3)
'On Error GoTo 0
'next_i:
Next i
Exit Sub
'----Error Handling----
'this works as expected when i=0 but not when i = 3,6,9
fixingErrors:
Err.Clear
Debug.Print "An error was generated for i= ", i
'On Error GoTo 0
'at this point I would expect my error state to be the same as the start of procedure?
theArray(i) = -1
Resume Next
End Sub
I'm using GoTo for error handling in an Access module and am getting a type mismatch error on the Procedures.HandleError call.
I tested to see if err is an Error:
Exit Sub
catch:
If IsError(err) Then
MsgBox "yes"
Else
MsgBox "no"
End If
Procedures.HandleError "ctrCreateSubject, frm_OnCreate", err, True
End Sub
and the MsgBox displays no and I can't figure out why. I'm using the same syntax in other places without problems
Can anyone help?
Let me expand a bit on my comment with an example.
The On Error GoTo statement will take care of the IsError() part since the procedure will jump to the catch label, only if there's an error. Therefore, if we do jump into the error handler, then we definitely have an error.
A sample error handler:
Sub Whatever()
On Error GoTo catch
'do something
Leave:
Exit Sub
catch:
'If we hit this point, then we definitely have an error.
'At this point, we can query the error number if we want to take action based on the error.
If Err.Number = xxxx Then
Msgbox "Error " & xxxx
End If
Resume Leave
End Sub
Then, there's another approach if you want to suspend the error handler and then query if an error occurred.
On Error Resume Next
'do something
If Err.Number <> 0
'An error occurred
End If
Which we can then clear if we want to do this again later on on our method.
Err.Clear
Lastly, keep in mind Err is a global object so you don't need to create an instance. Further info on MSDN: Err object
In a Word I am running a macro which triggers Error dialog sometimes. In PHP there I am used to use #command syntax which causes that in case of error the error is not printed to the output. Is there something similar to prevent the VBA debugger stop working?
My code is
Documents.Add.Content.Paste
and I want to create code, which would test if the result is valid, without breaking the debugger and printing the error, In the case that it failed, I would create timer 1s, and try the command again.
Edit:
Current code:
On Error GoTo ErrHandler
Documents.Add.Content.Paste
If False Then
ErrHandler:
' Add a delay
Dim tmpStart
tmpStart = Timer
Do
DoEvents
Loop While (tmpStart + 1) > Timer
Documents.Add.Content.Paste
End If
Currently error happens at line #11: 4605 - property not available because clipboard is empty or damaged
In your sub-routine or function, insert as the first line:
On Error Resume Next
Or if you want to handle the error:
On Error Goto ErrHandler
If False Then
ErrHandler:
MsgBox Err.Description
Exit Sub ' Or Exit Function
End If
Is a Resume always required after On Error handling ? Would it cause stack error to skip it by supressing the Resume line in the below example ?
Sub MySub
on error goto Hell
DoThis
DoThat
Otherstuff
Adios:
Exit Sub
Hell:
MsgBox Err.Description, vbCritical, "Error " & Err.Number
Resume Adios 'is this line required ?
Exit Sub
The Resume statement instructs VBA to resume execution at a specified point in the code. You can use Resume only in an error handling block; any other use will cause an error. Moreover, Resume is the only way, aside from exiting the procedure, to get out of an error handling block. Do not use the Goto statement to direct code execution out of an error handling block. Doing so will cause strange problems with the error handlers.
The Resume statement takes three syntactic form:
Resume
Resume Next
Resume <label>
Used alone, Resume causes execution to resume at the line of code that caused the error. In this case you must ensure that your error handling block fixed the problem that caused the initial error. Otherwise, your code will enter an endless loop, jumping between the line of code that caused the error and the error handling block. The following code attempts to activate a worksheet that does not exist. This causes an error (9 - Subscript Out Of Range), and the code jumps to the error handling block which creates the sheet, correcting the problem, and resumes execution at the line of code that caused the error.
On Error GoTo ErrHandler:
Worksheets("NewSheet").Activate
Exit Sub
ErrHandler:
If Err.Number = 9 Then
' sheet does not exist, so create it
Worksheets.Add.Name = "NewSheet"
' go back to the line of code that caused the problem
Resume
End If
The second form of Resume is Resume Next . This causes code execution to resume at the line immediately following the line which caused the error. The following code causes an error (11 - Division By Zero) when attempting to set the value of N. The error handling block assigns 1 to the variable N, and then causes execution to resume at the statement after the statement that caused the error.
On Error GoTo ErrHandler:
N = 1 / 0
Debug.Print N
Exit Sub
ErrHandler:
N = 1
' go back to the line following the error
Resume Next
The third form of Resume is Resume <label>: . This causes code execution to resume at a line label. This allows you to skip a section of code if an error occurs. For example,
On Error GoTo ErrHandler:
N = 1 / 0
code that is skipped if an error occurs
Label1:
more code to execute
Exit Sub
ErrHandler:
go back to the line at Label1:
Resume Label1:
All forms of the Resume clear or reset the Err object.
found this on
http://www.cpearson.com/excel/errorhandling.htm
Resume is not required you can call it if you like. Leaving it out of the example above will not cause any problems - the subroutine will just complete at the final Exit Sub.
I'm trying to cycle through a table in excel. The first three columns of this table have text headings, the rest of them have dates as headings. I want to assign those dates, sequentially, to a Date-type variable, and then perform some operations based on the date
To do this I am using a foreach loop on myTable.ListColumns. Since the first three columns do not have date headers, I have tried to set the loop up so that, if there is an error assigning the header string to the date-type variable, the loop goes straight to the next column
This seems to work for the first column. However, when the second column's header is 'assigned' to the date-type variable, the macro encounters an error even though it is within an error-handling block
Dim myCol As ListColumn
For Each myCol In myTable.ListColumns
On Error GoTo NextCol
Dim myDate As Date
myDate = CDate(myCol.Name)
On Error GoTo 0
'MORE CODE HERE
NextCol:
On Error GoTo 0
Next myCol
To reiterate, the error is thrown on the second round of the loop, at the statement
myDate = CDate(myCol.Name)
Can anyone explain why the On Error statement stops working?
With the code as shown, you're actually still considered to be within the error handling routine when you strike the next statement.
That means that subsequent error handlers are not allowed until you resume from the current one.
A better architecture would be:
Dim myCol As ListColumn
For Each myCol In myTable.ListColumns
On Error GoTo ErrCol
Dim myDate As Date
myDate = CDate(myCol.Name)
On Error GoTo 0
' MORE CODE HERE '
NextCol:
Next myCol
Exit Sub ' or something '
ErrCol:
Resume NextCol
This clearly delineates error handling from regular code and ensures that the currently executing error handler finishes before you try to set up another handler.
This site has a good description of the problem:
Error Handling Blocks And On Error Goto
An error handling block, also called an error handler, is a section of code to which execution is tranferred via a On Error Goto <label>: statement. This code should be designed either to fix the problem and resume execution in the main code block or to terminate execution of the procedure. You can't use the On Error Goto <label>: statement merely skip over lines. For example, the following code will not work properly:
On Error GoTo Err1:
Debug.Print 1 / 0
' more code
Err1:
On Error GoTo Err2:
Debug.Print 1 / 0
' more code
Err2:
When the first error is raised, execution transfers to the line following Err1:. The error hander is still active when the second error occurs, and therefore the second error is not trapped by the On Error statement.
You need to add resume of some sorts in your error handling code to indicate the error handling is over. Otherwise, the first error handler is still active and you are never "resolved."
See http://www.cpearson.com/excel/errorhandling.htm (specifically the heading "Error Handling Blocks And On Error Goto" and following section)
Follow-up to paxdiablo's accepted answer. This is possible, allowing two error traps in the same sub, one after the other :
Public Sub test()
On Error GoTo Err1:
Debug.Print 1 / 0
' more code
Err1:
On Error GoTo -1 ' clears the active error handler
On Error GoTo Err2: ' .. so we can set up another
Debug.Print 1 / 0
' more code
Err2:
MsgBox "Got here safely"
End Sub
Using On Error GoTo -1 cancels the active error handler and allows another to be set up (and err.clear doesn't do this!). Whether this is a good idea or not is left as an exercise for the reader, but it works!
Clearing all property settings of the Err object is not the same as resetting the error handler.
Try this:
Sub TestErr()
Dim i As Integer
Dim x As Double
On Error GoTo NextLoop
For i = 1 To 2
10: x = i / 0
NextLoop:
If Err <> 0 Then
Err.Clear
Debug.Print "Cleared i=" & i
End If
Next
End Sub
You'll notice the just like the OP, it will catch the error properly when i =1 but it will fail on line 10 when i = 2, even though we used Err.Clear
Dim ws As worksheets
For Each myCol In myTable.ListColumns
On Error GoTo endbit
Dim myDate As Date
myDate = CDate(myCol.Name)
On Error GoTo 0
'MORE CODE HERE
endbit:
Resume NextCol
NextCol:
' Next myCol
Exit Sub ' or something '