2

I went through some questions here like System.CalloutException: You have uncommitted work pending which provide answer where the callout can be split into asynchronous way.

Based on the information in below URL, https://help.salesforce.com/articleView?id=000079772&type=1

callout
insert
callout  <---- fails here

I am doing below:

callout
insert (this insert is based on response from first callout, response is being used in second callout and response also gets upserted in custom object in FUTURE method)
callout

Above scenario fails even though I am trying to split the insert operation into a Future/ Queueable method. I cannot have the second callout happening in asynchronous way as the response of second callout should be presented in UI

UseCase: 1) Callout - Retrieve accesstoken - valid for next 5 hours, so u want to reuse this 2) DML - Insert the accesstoken in custom object for reusing 3) Callout - Use the retrieved access token to get the actual response and pass to UI

When my method finds there is a valid active token in database, it is going to skip step 1 from above and proceed to Step 2 and 3. This scenario works perfectly.

It fails when there is no active token found and it has to begin from Step 1.

Any suggestions ?

  • How is this transaction invoked? Is this originating from a visualforce page? – Bob Lopez Jan 11 '18 at 21:19
  • Yes, it gets invoked from VF Page but there is one scenario where token is retrieved from custom object and is used for making callout. At this time, for some reason token gets invalidated, I will get 401 error from 2nd callout which means i need to get new access token before calling out 2nd time meaning complete step1 to 3 again !! – sf_user Jan 11 '18 at 21:34
  • 1
    It sounds like you don't need to do your insert before your second callout. Make callout 1, create custom sObject with token, but do not insert yet, make callout 2, then do your DML insert. (BTW, storing an access token in the database doesn't seem very secure) – John Thompson Jan 12 '18 at 3:00
2

Splitting the insert operation into a Future/Queueable method is (believe it or not!) actually performing an insert... an insert into the queue!

To solve your problem, do your first callout and the insert in the same transaction, then do your second callout in a Future/Queueable method.

The way I remember and avoid the dreaded "uncommited work pending" exception is for any given transaction, we have to do ALL the callouts first, followed by any DML (or queueing!) operation.

  • Thanks John for your reply. I understand your solution works and is also recommended approach. But as i said, I will need the response from the second callout. So i cannot have them in future/ Queueable – sf_user Jan 11 '18 at 21:35
  • Can you switch it to callout1, callout2, insert in some way? For example, set up all the fields of the objects about the be inserted, call any methods called by the trigger, do the second callout and finally insert the needed record? – Jochen Jan 11 '18 at 21:37
  • Thanks John. I think it makes more sense in my case to postpone DML to end of transaction after completing callouts – sf_user Jan 12 '18 at 3:22
4

Refer my blog post Visualforce webservice callout synchronously & asynchronously

If you want to achieve as follows and want to display the response of the 2nd callout on UI, without using Future/Queueable method then here is the approach.

callout
insert
callout

Approach

Create a Visualforce and command button action will call FirstCalloutAndSave method to perform callout and DML operation.

Then oncomplete method will perform perform2ndCallOut calling submit method (in a separate transaction) and results of the 2nd callout will be displayed on UI.

<apex:page id="MyVisualforcePage" controller="myExampleController"  showHeader="false">
    <apex:pageMessages id="msgId"/>
    <script>
        function performSecondCallOutMethod()
        {
             perform2ndCallOut();
        }
    </script>
    <apex:form id="myForm">
        <apex:actionFunction name="perform2ndCallOut" action="{!submit}" reRender="script-block,msgId"/>
        <!--fields on the UI -->
        <apex:commandButton value="Save" action="{!FirstCalloutAndSave}"
             oncomplete="return performSecondCallOutMethod();" reRender="msgId"/>
    </apex:form> 
</apex:page>

Controller

public class MyController
{

    public PageReference FirstCalloutAndSave()
    { 
         //1. perform firstout
         //2. perform DML
    }

    public PageReference submit()
    {
        try
        { 
           //perform 2ndCallout and show the result in the visualfore page
           return null
        }
        catch (Exception ex)
        {
           System.debug('Error in submit ' + ex);
                                ApexPages.Message msg = new ApexPages.Message(ApexPages.Severity.Error, ex.getMessage());
                                ApexPages.addMessage(msg);
                                return null;
        }
    }
}
  • This is even easier with Lightning components! – John Thompson Jan 12 '18 at 2:55

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

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