0

This question already has an answer here:

I got the following email: Sandbox: Developer script exception from

ContactTrigger: System.LimitException: Too many futurecalls: 51

Important addition: I do not see that mentioned 50 calls were executed in my HTTP call's handler logs.

Indeed I do some future calls upon contact inserting/updating in the loop, but I don't see so many contacts created in bulk in our Salesforce instance. Actually at the time I received this letter no contacts were created at all.

Here is some code:

    trigger ContactTrigger on Contact (after delete, after insert, after undelete, after update, before delete, before insert, before update) {

    ...

    if(tc.isAfter){
        // call handler instance to meet new registration requirements
        new ContactHelper().execute(tc);
    }
}

and the helper

public class ContactHelper extends GenericTrigger {

    public override void execute(TriggerContext tc)
    {
        if (Test.isRunningTest()) {
            return;
        }
        List<Contact> affectedContacts = tc.newList;
        if (tc.isInsert) {
            for (Contact contact: affectedContacts)
            {
                if (!contact.Greylist__c) {
                    updatePartnerPortal(contact.id, 'contact_inserted');
                }
            }
        }

        if(tc.isUpdate) {
            for (Contact contact: affectedContacts)
            {
                if (contact.PersonID__c == null && !contact.Greylist__c) {
                    updatePartnerPortal(contact.id, 'contact_updated');
                }
            }
        }
    }

    @future (callout=true)
    public static void updatePartnerPortal(string contactId, string operationType)
    {
        // HTTP call
    }
}

marked as duplicate by Pranay Jaiswal, Raul, Lukas Lunow, David Reed apex Mar 27 at 12:10

This question has been asked before and already has an answer. If those answers do not fully address your question, please ask a new question.

  • Same issue there, right. But due to missing white space in the email I got I could not find it be exact wording before asking. – Maksim Ramanovich Mar 27 at 10:21
  • Also, please, see my addition: I do not see that mentioned 50 calls were executed in my HTTP call's handler logs. – Maksim Ramanovich Mar 27 at 11:35
  • @MaksimRamanovich You will not see the results in your remote server's logs. Future methods are enqueued by Salesforce and executed once the transaction commits successfully. Because a limit error is thrown, the transaction doesn't commit and the future calls are rolled back (never executed). – David Reed Mar 27 at 12:10
  • That makes a great sense. Thank you – Maksim Ramanovich Mar 27 at 12:12
  • @DavidReed I would keep my question (unmark as duplicate) by two reasons: 1. It is searchable by the info received in exception email from Sforce; 2. Because of your explanation that future calls are enqueued - this was my main confusion, actually. – Maksim Ramanovich Mar 27 at 12:28
2

I would recommend you bulkify that @future method to accept a list<string> contactIds and string operationtype. Then, all you'd need do is inside your for loop if(tc.isUpdate) add each one to the list of contactIds. At the end of your trigger execution, you can then call your @future method if(!contactIds.isEmpty() == true). You'll need two lists, one for the isInsert and one for isUpdate. Otherwise you'd need to modify your @future method to accept a map.

0

This future call runs not only after insert but also after update according to this code.

    if(tc.isUpdate) {
        for (Contact contact: affectedContacts)
        {
            if (contact.PersonID__c == null && !contact.Greylist__c) {
                updatePartnerPortal(contact.id, 'contact_updated');
            }
        }
    }

Besides that it is really bad practice to not bulk such operations. In the end you'll constantly get such errors.

  • Nice catch, but the point is that due to condition it may be executed only once for object. But it makes sense, thanks – Maksim Ramanovich Mar 27 at 10:18
  • Also, please, see my addition: I do not see that mentioned 50 calls were executed in my HTTP call's handler logs. – Maksim Ramanovich Mar 27 at 10:52
0

If you are unable to perform bulk HTTP calls, you could use a chained queueable to perform one HTTP callout per queued job:

Pass the record Ids into the chained queueable implementation:

Set<Id> recordIds = new Set<Id>();
new ExampleChainedQueueable(recordIds).enqueueJob();

Implementation:

public class ExampleChainedQueueable extends AbstractChainedQueueable {

    public ExampleChainedQueueable(Set<Id> recordIds) {
        super(recordIds);
    }

    public override void processRecord(Id recordId) {
        // do HTTP call out here
    }

    public override AbstractChainedQueueable createJob(Set<Id> ids) {
        return new ExampleQueueable(ids);
    }
}

Abstract Chained Queueable class:

public abstract class AbstractChainedQueueable implements Queueable {

    private Set<Id> recordIds;

    public AbstractChainedQueueable(Set<Id> recordIds) {
        this.recordIds = recordIds;
    }

    public abstract void processRecord(Id recordId);

    public abstract AbstractChainedQueueable createJob(Set<Id> ids);

    public void execute(QueueableContext context) {

        if (recordIds.size() == 0) {
            return;
        }

        Id recordId = getNextInvoiceId();

        processRecord(recordId);

        processNextInvoice();
    }

    private void processNextInvoice() 
    {
        enqueueJob(this.recordIds);
    }

    private void enqueueJob(Set<Id> ids) {

        if (ids.size() == 0) {
            return;
        }

        // create next job
        AbstractChainedQueueable job = createJob(ids);

        // queue next job
        Id jobId = System.enqueueJob(job);

        System.debug(jobId);
    }

    public void enqueueJob() {

        // queue job
        Id jobId = System.enqueueJob(this);

        System.debug(jobId);
    }

    private Id getNextInvoiceId() {

        // get next record
        Id recordId;
        for(Id item : recordIds) {
            recordId = item;
            break;
        }

        // remove recordId
        recordIds.remove(recordId);

        return recordId;
    }
}

Source

Not the answer you're looking for? Browse other questions tagged or ask your own question.