ruby-concurrency/concurrent-ruby · error · ArgumentError
rescuers and block are both missing
Error message
rescuers and block are both missing
What it means
Promise#then builds a child promise whose fulfillment callback is either the given block or a rescuer callable given as the first positional argument (optionally an executor as second argument or via an options hash). If neither a rescuer nor a block is supplied, there is no callback to chain, so it raises ArgumentError 'rescuers and block are both missing'.
Source
Thrown at lib/concurrent-ruby/concurrent/promise.rb:324
# promise is rejected.
# @param [ThreadPool] executor An optional thread pool executor to be used
# in the new Promise
# @overload then(rescuer, executor: executor, &block)
# @param [Proc] rescuer An optional rescue block to be executed if the
# promise is rejected.
# @param [ThreadPool] executor An optional thread pool executor to be used
# in the new Promise
def then(*args, &block)
if args.last.is_a?(::Hash)
executor = args.pop[:executor]
rescuer = args.first
else
rescuer, executor = args
end
executor ||= @executor
raise ArgumentError.new('rescuers and block are both missing') if rescuer.nil? && !block_given?
block = Proc.new { |result| result } unless block_given?
child = Promise.new(
parent: self,
executor: executor,
on_fulfill: block,
on_reject: rescuer
)
synchronize do
child.state = :pending if @state == :pending
child.on_fulfill(apply_deref_options(@value)) if @state == :fulfilled
child.on_reject(@reason) if @state == :rejected
@children << child
end
child
end
View on GitHub (pinned to 0b88d5ff75)
Solutions
- Give then a block: promise.then { |v| v + 1 }
- Or a rescuer callable: promise.then(->(reason) { handle(reason) })
- When specifying an executor, still supply the callback: promise.then(executor: pool) { |v| v + 1 }
Example fix
# before
child = promise.then(executor: pool)
# after
child = promise.then(executor: pool) { |v| v + 1 } Defensive patterns
Strategy: validation
Validate before calling
raise ArgumentError, 'callback block or rescuer required' unless block_given? || rescuer
promise.then(on_fulfill_options) { |v| yield(v) } Type guard
->(obj) { obj.respond_to?(:call) } # valid rescuer for then(rescuer) Try / catch
begin promise.then(executor: executor, &callback) rescue ArgumentError => e raise ArgumentError, 'then needs a block or rescuer' if /rescuers and block/.match?(e.message) raise end
Prevention
- Always chain then with a callback; executor options alone are not enough
- When refactoring chains, delete then calls whose callback was removed rather than leaving them bare
- Remember then(rescuer, executor) positional order or use the executor: hash form plus a block
When it happens
Trigger: Bare promise.then; promise.then(executor: my_pool) — the options hash is consumed for the executor and no callback remains, which still raises; passing only args that are meant for the block before the block is written.
Common situations: Starting a chain and forgetting the callback mid-edit; passing executor options intending to configure the child without yet adding logic; refactoring then { |v| v }.then(...) chains down to a bare then.
Related errors
AI-assisted analysis of ruby-concurrency/concurrent-ruby@0b88d5ff75 (2026-08-21).
Data as JSON: /api/errors/5133917d2906b367.
Report an issue: GitHub.