Skip to main content

Sneak Peek: Visualforce with Javascript Promises

Visualforce with Javascript Promises

While working with Visualforce pages along with javascript remoting functionalities, we often create a lot of callback methods. On top of it, multiple invocations in a chained approach is more chaotic, often hardcoded and leaves code quite unreadable. 

Following is an example code while using Javascript remoting within Visualforce page:- 

function invoke_remoteService1(){
    console.log(' Invoking remote service 1');
    Visualforce.remoting.Manager.invokeAction('{!$RemoteAction.testWebServiceController.remoteService1}',
                                              remoteService1_ResponseHandler);
}

function remoteService1_ResponseHandler(response, event){
    console.log(' Response from remote service 1 = ' + response);
    if(response != null){
        console.log(' Invoking remote service 2');
        Visualforce.remoting.Manager.invokeAction('{!$RemoteAction.testWebServiceController.remoteService2}',
                                                  remoteService2_ResponseHandler);
    }
}

function remoteService2_ResponseHandler (response, event){
    console.log(' Response from remote service 2 = ' + response);
}


Similarly, callback methods keep on adding, making code readability and modularity an ever increasing challenge. As you can imagine, if we now want to add another button which calls same services in reverse order, we'll have to either write similar code twice or perform major code refactoring.

The solution is Javascript Promises.

What are Promises?

In simple words, a Promise promises to help chain (or bind) multiple asynchronous method invocations in same way as we chain synchronous method invocationsNO, it does not make your asynchronous calls as synchronous, it just helps you to structure your code in efficient way. Promises are a code design construct which help in structuring code in a better way, when dealing with asynchronous methods.

So, for example, we have synchronous methods (everything handled locally)

function validateCustomer(){ 
    //validation code
} 

function insertCustomer(){
    //insert customer 
} 

function createCustomer(){ 

    if(validateCustomer() == false){ 
        //insert customer
    }
    else{ 
        var customer = insertCustomer(); 
        if(customer != null){ 
            console.log('customer inserted successfully'); 
        } 
    } 
}


Previous code is very readable and modular, as we can bind it in different order if we wish to, without impacting existing functionality. It'll be great if we can bind our asynchronous methods in the same way. This is where Promises help.

Promises are extremely helpful while creating single page applications. As we rely more on javascript remoting calls, leading to more callback methods. This need is universal and not limited to Salesforce or visualforce, hence Promises have been added to ECMA6 (refer documentation at https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Promise) 

Note:- As of this time, IE doesn't support Promises in any version (refer documentation link shared previously)

Important points about Promises
  1. Once Promise concludes in success or failure, it'll remain in that state forever
  2. Outcome (or result) of promise can be used multiple times

How to implement promises?

Javascript Promises are now natively supported within all major browsers (except IE) means easier implementation with no external library to be loaded.

Promises can be implemented in two steps:- 
  1. Create promises – preferably create a promise each for each asynchronous method 
  2. Chain promises – now chain various promises together based on business requirement 

So, we can refactor our first code block as:

var testService1_Promise = new Promise( function (resolve, reject){
    console.log('Invoking remote web service 1');
    Visualforce.remoting.Manager.invokeAction('{!$RemoteAction.testWebServiceController.remoteService1}', function(response, event){
        if(event.statusCode == '200'){
            resolve(response);
        }
        else{
            reject();
        }
    });
});

var testService2_Promise = new Promise( function (resolve, reject){
    console.log('Invoking remote web service 2');
    Visualforce.remoting.Manager.invokeAction('{!$RemoteAction.testWebServiceController.remoteService2}', function(response, event){
        if(event.statusCode == '200'){
            resolve(response);
        }
        else{
            reject();
        }
    });
});

function getRemoteAccount() {
    
    //bind first service response with second web service
    testService1_Promise.then( 
        function (response){
            //handle response from remote web service 1
            console.log('response received = ' + response);
            
            //return promise for remote web service 2
            return testService2_Promise;
        },
        function (err){  
            console.log('---- error occurred = ' + err);
        }
    ).then(
        function (response){
            //handle response from remote web service 2
            console.log('response received = ' + response);
        },
        function (err){  
            console.log('---- error occurred = ' + err);
        }
    ).catch( function(err){
        console.log('----error occurred = ' + err);
    });
    
}

Note:- we have added catch block at the end. Promises allow us to define a catch handler to handle any unhandled exceptions.

Stateful Promises 
Previous code works, but has some issues, because:- 
  1. As you will notice, promises created above are objects. You will also notice that the asynchronous method (remote method invocation) is called while creating the object itself and at all subsequent usage, the promise will continue to pass same values 
  2. Promises are immutable. That is, each time you create a promise, it persists it's state as is 
  3. Off course, the code above is not very reusable and each time you have to do similar activities (probably in different order) you'll have to create those promises again 

So, a better way is to separate these steps 
  1. Create separate function for creating promises 
  2. Create separate function to bind promises. Create promises dynamically within these functions 

For e.g. previous code can be refactored as:- 


function getRemoteService1Promise(){
    return new Promise( function (resolve, reject){
        Visualforce.remoting.Manager.invokeAction('{!$RemoteAction.testWebServiceController.remoteService1}', function(response, event){
            resolve(response);
        });
    });
}
    
function getRemoteService2Promise (){
    return new Promise( function (resolve, reject){
        Visualforce.remoting.Manager.invokeAction('{!$RemoteAction.testWebServiceController.remoteService2}', function(response, event){
            resolve(response);
        });
    });
}
    
function getRemoteAccount() {
        
    var RemoteService1Promise = getRemoteService1Promise();    
        
    RemoteService1Promise.then( 
        function (response){
            console.log('response received = ' + response);
            return getRemoteService2Promise();
        },
        function (err){  
            console.log('---- error details 1 = ' + err);
        }
    ).then(
        function (response){
            console.log('response received = ' + response);
        },
        function (err){  
            console.log('---- error details 2 = ' + err);
        }
    ).catch( function(err){
        console.log('---- exception details 3 = ' + err);
    });        
}
     


Parallel vs Serial method invocation

Do notice, in previous code, instance of second promise is created when successful response is received for first asynchronous call. This means that second asynchronous method is invoked only when first one succeeds. In other words, it performs a serial-mode chaining of asynchronous events.

However, if both the asynchronous methods are exclusive and can be invoked in parallel, we can modify our code to create promises for both asynchronous methods together. This way we can achieve parallel method invocation for asynchronous methods.

Benefits of using Promises

  1. First and foremost, the code is reusable and separates asynchronous method invocation and their sequence / post-processing. So, you can choose to bind it in any way needed by business requirements
  2. Code is very readable
  3. Better exception handling

References


Comments

Post a Comment

Popular posts from this blog

Lightning: Generate PDF from Lightning components with in-memory data

I'm sure as everyone is diving into lightning components development, they are getting acquainted with the nuances of the Lightning components framework. As well as, its current limitations. Being a new framework, this is bound to happen. Although we have our users still using salesforce classic, we have started using lightning components framework our primary development platform and Visualforce is considered primarily for rendering lightning components within Classic Service console.
Recently, while re-architecting a critical module, we encountered a problem wherein we needed to generate PDF from lightning components. Now, being Javascript intensive framework, it has limited room for such features (may be included in future roadmap). As of now, there is no native feature within the lightning framework to do so (at least I didn't find anything).

Common Scenario - Create Visualforce page to retrieve data and generate PDF For scenarios where the data exist within Salesforce, it…

Quick Tips: Salesforce default Images

Well, I'm sure a lot of you still rely on using out of the box salesforce images for displaying quick icons within formula fields or even using them within your Visualforce pages. Lately, I realized that a lot of earlier resources are no longer accessible, so I tried to quickly extract all images from Salesforce CSS files and provide a quick reference here.

Please note, I've referenced all images from SF servers directly, so if anything changes, the image should stop rendering here. As these images are completely controlled by Salesforce, and in case they change anything, it might lead to image not being accessible.

Image pathImage/img/func_icons/arrowright10.gif/img/google/gmail_M_icon.gif/img/func_icons/ispan12.gif/img/func_icons/schedReport34.gif/img/func_icons/util/dialogClose16.gif/img/func_icons/util/rescale11.gif/img/overlaypointer.gif/img/msg_icons/confirm16.png/img/msg_icons/confirm24.png/img/msg_icons/confirm32.png/img/msg_icons/securityconfirm48.gif/img/msg_icons/er…

Lightning: Generate PDF within Lightning Experience with Salesforce Data

Some time back I posted a solution to generate PDF from Lightning components using in-memory data.
Post url:http://www.vermanshul.com/2017/07/lightning-generate-pdf-from-lightning.html

It was developed for a specific scenario, wherein we need to generate PDF where:
User interface is Salesforce classicInitiated via Lightning ComponentData doesn't exist within Salesforce and is completely in-memory As complex and tricky this situation was, we did end up finding a stable and equally tricky solution.

However, I realize that there are still lack of solutions (or maybe my search skills are downgrading) to generate and automatically download PDF document from Lightning Experience, without using any lightning components, wherein data exists within Salesforce. You can use the earlier solution in that case, but it will be an overkill.

There are various solutions available to generate PDF from javascript. But, I still think the plain old method of converting HTML to PDF (via visualforce PDF g…