Ruby: Continue a loop after catching an exception - ruby

Basically, I want to do something like this (in Python, or similar imperative languages):
for i in xrange(1, 5):
try:
do_something_that_might_raise_exceptions(i)
except:
continue # continue the loop at i = i + 1
How do I do this in Ruby? I know there are the redo and retry keywords, but they seem to re-execute the "try" block, instead of continuing the loop:
for i in 1..5
begin
do_something_that_might_raise_exceptions(i)
rescue
retry # do_something_* again, with same i
end
end

In Ruby, continue is spelt next.

for i in 1..5
begin
do_something_that_might_raise_exceptions(i)
rescue
next # do_something_* again, with the next i
end
end

to print the exception:
rescue
puts $!, $#
next # do_something_* again, with the next i
end

Related

Is there a built-in way to check if #next or #peek will raise StopIteration?

I'm working with a few iterators where I have to do something along these lines (enum is an enumerator)
enums_with_zero << enum.rewind if enum.peek == 0
which normally works fine, but this is after there's already been #next called on the enum a few times. The issue with this is that the enum could be at the end and with a few values passed for enum, I have hit the issue where enum.peek raises StopIteration because the enum is completed. Is there a way I could put in a guard to check if enum.peek or enum.next will cause StopIteration before I call it. Something that would have the behavior of this for example?
class Enumerator
def has_next?
begin
peek && true
rescue StopIteration
false
end
end
end
You can rescue the StopIteration explicitly, but there's also the idea that the loop method internally rescues a StopIteration exception by simply exiting the loop. (Inside loop, raise StopIteration has the same effect as break.)
This code simply exits the loop when you try to peek past the end:
a = %w(a b c d e).to_enum
loop do
print a.peek
a.next
end
The code outputs abcde. (It also transparently raises and rescues StopIteration.)
So, if you want to simply ignore the StopIteration exception when you try to peek past the end, just use loop.
Of course, once you peek past the end, you'll get dumped out of the loop. If you don't want that, you can use while and rescue to customize behavior. For example, if you want to avoid exiting if you peek past the end, and exit when you iterate past the end using next, you could do something like this:
a = %w(a b c d e).to_enum
while true
begin
print a.peek
rescue StopIteration
print "\nTried to peek past the end of the enum.\nWe're gonna overlook that.\n"
end
x = a.next rescue $!
break if x.class == StopIteration
end
p 'All done!'
The last two lines in the loop do the same thing as this, which you could use instead:
begin
a.next
rescue StopIteration
break
end
A point to make is that handling StopIteration is Ruby's intended way of dealing with getting to the end of an iterator. Quoting from Matz's book The Ruby Programming Language:
External iterators are quite simple to use: just call next each time you want another
element. When there are no more elements left, next will raise a StopIteration exception.
This may seem unusual—an exception is raised for an expected termination
condition rather than an unexpected and exceptional event. (StopIteration is a descendant
of StandardError and IndexError; note that it is one of the only exception
classes that does not have the word “error” in its name.) Ruby follows Python in this
external iteration technique. By treating loop termination as an exception, it makes
your looping logic extremely simple; there is no need to check the return value of
next for a special end-of-iteration value, and there is no need to call some kind of
next? predicate before calling next.

Implement 'rescue' without 'next'

I have this example to handle errors and continue code execution:
begin
p '-' * 100
request_builder.new(env: tested_env).submit!
rescue => error
error_logs << "#{error}\n#{error.backtrace.first(5).join("\n")}"
next
end
How can I rewrite the code without next?
I found this example:
%w(1 2).each do |x|
p x
begin
raise 'something'
rescue => error
p error
end
end
=>
"1"
#<RuntimeError: something>
"2"
#<RuntimeError: something>
How can it be used to implement the code?
The next in you example has nothing to do, with a normal rescue block. It just tells to go to the next iteration. So remove your next statement and you are find.
begin
p '-' * 100
request_builder.new(env: tested_env).submit!
rescue => error
error_logs << "#{error}\n#{error.backtrace.first(5).join("\n")}"
# next # removing next, removes you error,
# you just put whatever you want in your rescue block,
# the application continues in this block
end

ruby: finish loop iteration before raising Interrupt

I'm looping through a lot of items and I want to periodically interrupt the loop to save and continue at a later time like this:
begin
big_list.each do |i|
# sensitive stuff
sensitive_method(i)
# other sensitive stuff
end
rescue Interrupt
# finish the current iteration
# then do something else (save)
# don't raise (print done)
end
By sensitive I mean that, if Interrupt is raised in the middle of an iteration, data will be corrupted so I need to guarantee that the iteration finishes before exiting.
Also, if another exception is raised, it should still finish the loop but raise it afterwards
EDIT:
Using the answer by mudasobwa in a test scenario:
while true
result = begin
puts "start"
sleep 1
puts "halfway"
sleep 1
puts "done\n\n"
nil
rescue Exception => e
e
end
case result
when Interrupt
puts "STOPPED"
break
when Exception then raise result
end
end
I get:
start
halfway
done
start
^C: /...
STOPPED
which is my exact problem, I need it to finish the loop (sleep, print halfway, sleep, print done) and only then break out (wrapping the puts, sleep... in a method does not help)
TL;DR: There is no way to continue the execution of the method from inside the middle of it.
big_list.each do |i|
# sensitive stuff
result = begin
sensitive_method(i)
nil
rescue Exception => e
e
end
# other sensitive stuff
case result
when Interrupt
puts "done"
break "done"
when Exception then raise result
end
end
Sidenote: you probably don’t want to rescue the topmost Exception, but some subclass that makes sense to rescue.
To make it possible to finish the chunk of operations:
operations = [
-> { puts "start" },
-> { sleep 1 },
-> { puts "halfway" },
-> { sleep 1 },
-> { puts "done\n\n" }
]
def safe_chunk(operations, index = 0)
result = operations[index..-1].each_with_index(index) do |op, idx|
begin
op.()
rescue Exception => e
safe_chunk(operations, idx) # or idx + 1
break e
end
end
result.is_a?(Array) ? nil : result
end
The Interrupt exception is raised in the main thread. If you use a worker thread to process the list it will never be interrupted. You will need a way to tell the worker thread to terminate though. Rescuing Interrupt in the main thread and setting a flag that's checked by the child can accomplish this.
BigList = (1..100)
def sensitive_method(item)
puts "start #{item}"
sleep 1
puts "halfway #{item}"
sleep 1
puts "done #{item}"
puts
end
#done = false
thread = Thread.new do
begin
BigList.each do |item|
break if #done
sensitive_method item
end
end
end
begin
thread.join
rescue Interrupt
#done = true
thread.join
end
The keyword ensure, used in rescue clauses, is available for situation such as this one, where code must be executed after an exception occurs.
[-1, 0, 1].each do |i|
begin
puts "i=#{i} before exception"
# <additional code>
n = 1/i
rescue ZeroDivisionError => e
puts "Exception: #{e}"
exit
ensure
puts "Just executed 1/#{i}"
# <additional code>
end
end
i=-1 before exception
Just executed 1/-1
i=0 before exception
Exception: divided by 0
Just executed 1/0
Notice that begin/rescue/ensure/end must be inside the loop and that the code after ensure is executed for each i regardless of whether a zero-divide exception occurs.

In Ruby, can you create a rescue for twitter for when there is an error it will continue the loop?

I'm trying to create a rescue that if and when there is an Twitter::Error::NotFound error (such as does not exist) it will just keep going through the loop. Please help, thanks.
Below is the code,
begin
File.open("user_ids.txt") do |file|
file.each do |id|
puts client.user("#{id}").screen_name
rescue Twitter::Error::NotFound => error
next # skip this item
end
end
end
Instead of the retry method is there a a method that can skip and keep moving on to the next item in the loop?
I'm pretty sure the error.rate_limit does not apply (I copied this code from a different rescue call), is there another method to call? like error.notfound.continue_with_loop
I would like to create a rescue that if and when there is an error such as does not exist so it will just keep going through the loop. Please help, thanks.
yes next will continue and retry the next item in a loop.
retry will retry the loop with the same item.
Note: you don't have enough ends for all the do that are in that method. So I'd try:
begin
File.open("user_ids.txt") do |file|
file.each do |id|
puts client.user("#{id}").screen_name
rescue Twitter::Error::NotFound => error
sleep error.rate_limit.reset_in + 1
next # skip this item
end
end
end
Note: see how proper indentation makes it clear when you're missing an end ?
You may need to shift the begin/end block that is currently around the lot - to just be around the code that you want to rescue-from (or it'll default to the outside begin/end rather than your loop)
File.open("user_ids.txt") do |file|
file.each do |id|
begin
puts client.user("#{id}").screen_name
rescue Twitter::Error::NotFound => error
sleep error.rate_limit.reset_in + 1
next # skip this item
end
end
end

ruby rescue block -- respond with more than just one command

I'm running a script with an API that often times out. I'm using begin/rescue blocks to get it to redo when this happens, but want to log what is happening to the command line before I run the redo command.
begin
#...api query...
rescue ErrorClass
puts("retrying #{id}") && redo
end
Unfortunately the above script doesn't work. Only the first command is run.
I would like to force the rescue block to run multiple lines of code like so:
begin
# api query
rescue ErrorClass do ###or:# rescue ErrorClass do |e|
puts "retrying #{id}"
redo
end
but those don't work either.
I've had luck creating a separate method to run like so:
def example
id = 34314
begin
5/0
rescue ZeroDivisionError
eval(handle_zerodiv_error(id))
end
end
def handle_zerodiv_error(id)
puts "retrying #{id}"
"redo"
end
...that actually works. But it requires too many lines of code in my opinion and it uses eval which is not kosher by any means according to my mentor(s).
You are unnecessarily complicating things by using && or do. The && version does not work because puts returns nil, so by shortcut evaluation of &&, the part to follow is not evaluated. If you use || or ; instead, then it will work:
begin
...
rescue ErrorClass
puts("retrying #{id}") || redo
end
begin
...
rescue ErrorClass
puts("retrying #{id}"); redo
end
but even this is not necessary. You somehow seem to believe that you need a block within rescue to write multiple lines, but that does not make sense because you are not using a block with single line. There is no Ruby construction that requires a block only when you have multiple lines. So, just put them in multiple lines:
begin
...
rescue ErrorClass
puts("retrying #{id}")
redo
end
There is a retry built in. This example is from "The Ruby Programming Language" pg 162.
require "open-uri"
tries = 0
begin
tries +=1
open("http://www.example.com/"){|f| puts f.readlines}
rescue OpenURI::HTTPError => e
puts e.message
if (tries < 4)
sleep (2**tries) # wait for 2, 4 or 8 seconds
retry # and try again
end
end

Resources