rescue(): One-Line Graceful Error Handling for Flaky Code Paths
Every codebase has a few calls that are "probably fine but might not be": a third-party geocoding lookup, an optional analytics ping, a currency conversion API. When one of those fails, you usually don't want to blow up the whole request, you want to log it and move on with a sensible default. That's exactly what Laravel's rescue() helper is for.
The manual version
try {
$rate = $exchangeRateApi->getRate('USD', 'EUR');
} catch (\Throwable $e) {
report($e);
$rate = 1.0;
}
Four lines, and you'll write some version of it a dozen times across a project.
With rescue()
$rate = rescue(
fn () => $exchangeRateApi->getRate('USD', 'EUR'),
rescue: 1.0
);
rescue() runs the closure, catches any Throwable, reports it through your normal exception handler (so it still shows up in Sentry, Flare, or the log driver you've configured), and returns the fallback value instead of letting the exception propagate.
The fallback can be a closure too
If computing the default itself is expensive or needs context, pass a closure instead of a static value:
$shippingEstimate = rescue(
fn () => $carrierApi->estimate($order),
rescue: fn () => ShippingEstimate::flatRateFallbackFor($order)
);
The fallback closure only runs if the primary one throws, so you're not paying for both code paths on the happy path.
Turning off reporting
Sometimes a failure is expected and noisy enough in your error tracker that you don't want it reported at all, just swallowed with a default:
$avatarUrl = rescue(
fn () => $gravatarClient->urlFor($user->email),
rescue: asset('images/default-avatar.png'),
report: false
);
Where this pattern earns its keep
At PhpArchitect we use rescue() heavily around anything that talks to a third-party service where a failure shouldn't be a user-facing 500: shipping rate lookups, optional enrichment calls, non-critical webhooks fired on the way out of a request. It keeps the "happy path plus fallback" logic readable in one expression instead of a multi-line try/catch block that clutters up a controller or action class.
When not to use it
rescue() is for genuinely optional operations. If the failure means the request truly can't succeed, for example your payment charge call, let the exception propagate and handle it explicitly. Swallowing an exception on a critical path just to avoid a try/catch is how you end up debugging "why did this order get created with no payment" six months later with nothing in the logs to explain it.