1

I've recently been asked to convert a method in our account trigger to a future method to help reduce CPU errors during execution. When attempting to run all test classes in my org (for a completely unrelated project), I found that the test classes for our batches that insert or update accounts started seeing this error:

caused by: System.AsyncException: Future method cannot be called from a future or batch method: class.method(Set<Id>)

That makes sense to me. Pretty straight forward. The update is happening in a batch and a future method is being called in the same context so, error.

What concerns me is that future methods in triggers are a common practice and batch api use is also common practice. This almost seems like it might be a good best practice to not use future methods in triggers at all as they essentially run the risk of breaking batches (or don't use batches).

The question is: What is the best practice (or is it even possible) to handle triggers asynchronously without impacting other asynchronous operations such as scheduled batches?

3 Answers 3

3

Check if you are already in an asynchronous process before calling your future method. Here's the basic idea sketched out:

public static class MyClass
{
    static Boolean shouldProcessAsync()
    {
        return !system.isFuture() && !system.isBatch() && !system.isQueueable() &&
            Limits.getLimitFutureCalls() > Limits.getFutureCalls();
    }

    public static void doStuff(List<MyObject__c> records)
    {
        if (records.isEmpty()) return;

        if (shouldProcessAsync())
        {
            doStuffAsync(new Map<Id, SObject>(records).keySet());
        }
        else
        {
            // logic
        }
    }
    @future
    static void doStuffAsync(Set<Id> recordIds)
    {
        doStuff([
            SELECT Name
            FROM MyObject__c
            WHERE Id IN :recordIds
        ]);
    }
}
1

I use a handler class, which takes a job (of a specific interface type with aHandle method), then checks the context before running it. The check is really simple - all the values are exposed through System & Limits, and line up with the @future documentation. If the context is one where I can run the future method async, I do so- otherwise the jobs are handled in sync. The bones of this check are very similar to what Adrians posted above.

If I ever need to change the context entry, I can do so is this one location, as well as any other changes, such as error reporting or logging. Its turned out to be very handy to manage changes across a code base without requiring the implementation to know too much about when or how it can be run.

Even if you don't have a handler to manage your jobs, having a single method somewhere, like CanRunFuture() with these checks can be enough. Adding them back in can be a bit of a pain tho.


I can't share the exact code, but if you have questions about using a handler class to manage @future jobs or other async management, feel free to ask.

0

You may also find that changing your future methods to Queueable classes works better for your use case. While they do require a bit more code to set up (although I believe they can be inner classes, if you want to localize them to your Trigger Handler). While there are limits in terms of how Queueable classes can be enqueued during transactions, they can generally be enqueued during other asynchonous transactions (including other Queueables).

You must log in to answer this question.

Not the answer you're looking for? Browse other questions tagged .