MrBungleBear
MrBungleBear

Reputation: 53

ruby: continue in same loop iteration after an exception

I feel a bit odd, as there are a number of questions a bit like this, but not close enough, or no answer.

[EDIT: I am rephrasing the question to make it clearer]

I have a loop that does a number of things, and was playing with it to see if various options would work to make things more readable, cleaner. In the end, I messed something up and it threw and exception, which was caught with rescue. So far so good. I cannot, however, find any way to make ruby just continue with the following statements in that same iteration of the loop - the exception always casues the following statements and it moves on to the next iteration - default behaviour. Redo or retry would be pointless and result in an infinite loop, hitting the same error and exception again, etc,

I there any way to force ruby to handle the error in some way then just continue where it left off?

Here is a sample, stolen from I don't know where and adapted.

3.times do |i|
  begin
    # first bunch of stuff to do
    # second bunch of stuff to do
    # third bunch of stuff to do
    # fourth bunch of stuff to do
  rescue => e
    p e
  end
end

Basically, in this example, I want all four of the bunches of stuff to be executed before moving on to the next iteration of the loop, even if one of them should cause an error. I wasn't to process the error, allowing me to see the exception result, etc, but effectively ignoring it and continuing on to do all that other bunches of stuff.

Any ideas? It might be something really obvious that I have just never seen before.

Upvotes: 4

Views: 9184

Answers (2)

jpn
jpn

Reputation: 796

You're defining a begin-end block with a rescue clause. If any exception is raised by the block and there's a matching rescue clause, the block will stop executing and the rescue clause will be executed. If there's no matching rescue block, the error will bubble up (and hopefully be handled by another block, otherwise it'll be unhandled and your script will stop!) If there's an ensure clause, that'll get run too even if there's an exception.

So where does that leave us? If you want to defend against failure of individual steps and continue regardless, each step needs its own block:

3.times do |i|
  begin
    first_thing
  rescue => e
    puts "The first thing blew up! #{e.inspect}"
    puts "I'll carry on anyway ¯\\_(ツ)_/¯"
  end

  begin
    second_thing
    third_thing
  rescue => e
    puts "Either the second or third thing blew up... #{e.inspect}"
    puts "Straight on to the fourth thing!"
  end

  begin
    fourth_thing
  rescue => e
    puts "Fourth thing blew up! #{e.inspect}"
  end
end

It's a bit unusual to have a block like this which should carry on executing if something goes wrong - that's usually a good way to make one of the next steps go wrong too! You might need to make sure that you still have data integrity at each point, and that the later steps don't depend on something which might have failed to happen in the earlier steps.

Upvotes: 3

Fire Lancer
Fire Lancer

Reputation: 30115

A begin block (including implicit ones from say def) ends on the first exception. If you want to do somthing seperately after regardless of success/failure, then put it outside the block.

3.times do |i|
  begin
    raise "Raised from iteration #{i}"
  rescue => e
    p e
  end
  puts "I'm after the exception"
end

If you want to do something even after a return, break, etc. then use an ensure block.

5.times do |i|
  begin
    break if i == 3
    raise "Raised from iteration #{i}"
  rescue => e
    p e
  ensure
    puts "I always run #{i}"
  end
end

Which outputs:

#<RuntimeError: Raised from iteration 0>
I always run 0
#<RuntimeError: Raised from iteration 1>
I always run 1
#<RuntimeError: Raised from iteration 2>
I always run 2
I always run 3

If you really want to ignore and continue from many statements that might throw, they each need a seperate rescue block. You could do this with another wrapper method. However be very careful that the exception being ignored really do not matter than that continuing is safe.

Exception by design, are meant to abort the following operations and try to avoid leaving inconsistent and unknown program states.

def suppress
  yield
rescue => e
  puts "Supressing #{e}"
end
5.times do |i|
  suppress { raise "I throw sometimes #{i}" if i <= 3 }
  suppress { raise "I throw sometimes too #{i}" if i > 2 }
  puts "After possible exceptions #{i}"
end

Outputs:

Supressing I throw sometimes 0
After possible exceptions 0
Supressing I throw sometimes 1
After possible exceptions 1
Supressing I throw sometimes 2
After possible exceptions 2
Supressing I throw sometimes 3
Supressing I throw sometimes too 3
After possible exceptions 3
Supressing I throw sometimes too 4
After possible exceptions 4

Upvotes: 2

Related Questions