0

I have a schedulable class that makes a series of callouts to an external email service. I want to take info from the first call out and make another. This code below is part of the SendGridApi Class that is called from the mailScheduler class. I getting an error about future callout from future callout. How should I avoid these issues?

   // check if test is running
   if (!Test.isRunningTest()) {
        res = h.send(req); 
        // if status = 201 and the endpoint was used to make a campaign
        if (res.getStatusCode() == 201 && endpoint == '/v3/campaigns') {

            response = res.getBody(); 

            // deserialize res.body into map
            Map<String, object> results = (Map<String, object>) JSON.deserializeUntyped(res.getBody());

            //get sender id from response                   
            object idstring = results.get('sender_id');
            integer senderid = integer.valueOf(idstring);

            //get campaign id from response to schedule it later.
            object campaignstring = results.get('id');

            string endpoint2= '/v3/campaigns/' + campaignstring + '/schedules'; 

            DateTime myDateTime = datetime.now(); // get current time

            myDateTime= myDateTime.addHours(12); // add 12 hours

            long longtime= myDateTime.getTime(); //get epoch time code in miliseconds

            long shorttime= longtime/1000; // convert time to seconds

            Map<String, Object> mapped = new Map<String, Object>{'send_at'=> shorttime};

            mailScheduler.sendit(JSON.serialize(mapped), endpoint2, 'POST');
            }

Here is the mailScheduler class.

global class mailScheduler {


public static void sendIt(string jsonbody, string endpoint, string httpmethod){


        if (!Test.isRunningTest()) {
        SendGridAPI.sendRequest(JSONbody, endpoint, httpMethod);
        }  

  }
}

Here is the solution that worked for me.

 // check if test is running
   if (!Test.isRunningTest()) {
        res = h.send(req); 
        // if status = 201 and the endpoint was used to make a campaign
        if (res.getStatusCode() == 201 && endpoint == '/v3/campaigns') {

            response = res.getBody(); 

            // deserialize res.body into map
            Map<String, object> results = (Map<String, object>) JSON.deserializeUntyped(res.getBody());

            //get sender id from response                   
            object idstring = results.get('sender_id');
            integer senderid = integer.valueOf(idstring);

            //get campaign id from response to schedule it later.
            object campaignstring = results.get('id');

            string endpoint2= '/v3/campaigns/' + campaignstring + '/schedules'; 

            DateTime myDateTime = datetime.now(); // get current time

            myDateTime= myDateTime.addHours(12); // add 12 hours

            long longtime= myDateTime.getTime(); //get epoch time code in miliseconds

            long shorttime= longtime/1000; // convert time to seconds

            Map<String, Object> mapped = new Map<String, Object>{'send_at'=> shorttime};

            //Instantiate an httpRequest and set the required attributes
            httpRequest req2 = new httpRequest();


            req2.setMethod('POST');
            req2.setEndpoint(endpointBase + endpoint2);
            system.debug('^e^n^d^POINT2^ '+endpointBase + endpoint2);

            // format headers 
            req2.setHeader('Authorization', keyBearer);
            req2.setHeader('content-type', 'application/json');

            // set json body from 
            req2.setBody(json.serialize(mapped));

            // prepart to send request
            httpresponse res2 = new httpresponse();
            Http h2 = new Http();
            res2 = h.send(req2); 

              }
4
  • What is the exact error message?
    – metasync
    Sep 24, 2017 at 21:24
  • FATAL_ERROR System.AsyncException: Future method cannot be called from a future or batch method: SendGridAPI.sendRequest(String, String, String) Sep 24, 2017 at 21:57
  • Why? You shouldn't be in such situation in first place. @future is needed only if you're in a trigger or performed DML operations before the callout (the "you have uncommitted work pending..." error). Can you inspect the SendGridApi class and see if there are communication methods not marked @future? Call these versions / make such versions if needed. Even if something there comes from managed package the global (usable by your code) methods should be visible in Setup.
    – eyescream
    Sep 24, 2017 at 22:22
  • I worked out a solution by making the callout in the if statement. Sep 24, 2017 at 23:02

1 Answer 1

2

As you found, you can't chain future method calls together. I.e. a future method can't invoke another future method.

Assuming all the code is under your control you can just wrap in a System.isFuture() check. If it is already in a future context call a variation of the method in question that doesn't have the @future annotation.

That should serve for the majority of scenarios. If you still need to split out to another asynchronous methods have a look at Queueable Apex.


As an aside, see if you can get this out of your code.

if (!Test.isRunningTest()) {
    // lots of code here.
}

That's likely to come back to bite you sooner or later. A mock response should make this code testable.

2
  • I had an issue with my scheduled job not running trying to use a non-future method to make a callout from a scheduled class. Sep 25, 2017 at 3:10
  • Correct, you can perform a directly callout from a scheduled class. Typically you will switch out to a future or batch method. But if you are already in a future method you can't call another future method. Sep 25, 2017 at 7:27

You must log in to answer this question.

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