9

When I read about promises, I see that they should appear as solution to prevent callback hell and pyramid of doom but when I actually try to write a code which would use Lightning promises it still doesn't look like synchronous code, it still resembles both pyramid of doom and callback hell.

main: function(component, helper) {
    helper.promise1(component, helper).then(
        $A.getCallback(function(record) {
            return helper.promise2(component, helper, record).then(
                $A.getCallback(function(){
                    console.log("Success");
                    helper.showToastMessage(component, "Great Success!!!!")
                }), 
                $A.getCallback(function(errorMessage){
                    console.log("Rejected by second promise2");
                    helper.showToastMessage(component, "Failure " + errorMessage);
                })

            )
        }),

        $A.getCallback(function(errors){
            console.log("Rejected by first promise1");
            let message = errors && Array.isArray(errors) && errors.length > 0 ? errors[0].message :'Unknown error';
            helper.showToastMessage(component, "Failure " + message);
        })

    )
}

Is that possible to refactor this code to look more like synchronous code and without callback hell or pyramid of doom?

15

You have two basic problems. First, you don't need $A.getCallback unless you need to call component.set. At all other points, it's superfluous.

main: function(component, helper) {
    helper.promise1(component, helper).then(
        function(record) {
            return helper.promise2(component, helper, record).then(
                function(){
                    console.log("Success");
                    helper.showToastMessage(component, "Great Success!!!!")
                }, 
                function(errorMessage){
                    console.log("Rejected by second promise2");
                    helper.showToastMessage(component, "Failure " + errorMessage);
                }
            )
        },
        function(errors){
            console.log("Rejected by first promise1");
            let message = errors && Array.isArray(errors) && errors.length > 0 ? errors[0].message :'Unknown error';
            helper.showToastMessage(component, "Failure " + message);
        }
    )
}

Next, you typically chain a then from the previous then:

main: function(component, helper) {
    helper.promise1(component, helper).then(
        function(record) {
            return helper.promise2(component, helper, record);
        },
        function(errors){
            console.log("Rejected by first promise1");
            let message = errors && Array.isArray(errors) && errors.length > 0 ? errors[0].message :'Unknown error';
            helper.showToastMessage(component, "Failure " + message);
        }
    ).then(
        function(){
            console.log("Success");
            helper.showToastMessage(component, "Great Success!!!!")
        }, 
        function(errorMessage){
            console.log("Rejected by second promise2");
            helper.showToastMessage(component, "Failure " + errorMessage);
        }
    )
}

Also, you typically want to use catch instead of the two-parameter then version, which lets you have as single error handler:

main: function(component, helper) {
    helper.promise1(component, helper).then(
        function(record) {
            return helper.promise2(component, helper, record);
        }
    ).then(
        function(){
            console.log("Success");
            helper.showToastMessage(component, "Great Success!!!!")
        }
    ).catch(
        function(errorMessage){
            console.log("Error occuring during chain");
            helper.showToastMessage(component, "Failure " + errorMessage);
        }
    );
}

As a bonus, arrow functions makes things a bit more legible:

main: function(component, helper) {
    helper.promise1(component, helper).then(
        record => helper.promise2(component, helper, record);
    ).then(
        () => {
            console.log("Success");
            helper.showToastMessage(component, "Great Success!!!!")
        }
    ).catch(
        (errorMessage) => {
            console.log("Error occuring during chain");
            helper.showToastMessage(component, "Failure " + errorMessage);
        }
    );
}

After that, you might want to remove the debug logs for production use:

main: function(component, helper) {
    helper.promise1(component, helper).then(
        record => helper.promise2(component, helper, record)
    ).then(
        () => helper.showToastMessage(component, "Great Success!!!!")
    ).catch(
        errorMessage => helper.showToastMessage(component, "Failure " + errorMessage)
    );
}

This is what you'd typically see in production-ready code (plus comments, as necessary).

  • Is that possible to use arrow functions in Lightning? – Patlatus Jan 12 '18 at 17:38
  • @Patlatus Yes, you can use arrow functions in Lightning. – sfdcfox Jan 12 '18 at 17:40
  • @sfdcfox how are you using arrow functions, I have not seen them work in Lightning? – Mike Havrilla Mar 6 '18 at 22:59
  • @MikeHavrilla You can't use arrow functions for controller/helper methods, but you can use them as callbacks, which I tend to do because they're incredibly handy. – sfdcfox Mar 6 '18 at 23:14
4

Looks like I have found some possible solution to beautify this code.

main: function(component, helper) {
    helper.promise1(component, helper).then(
        $A.getCallback(helper.promise2),

        $A.getCallback(function(errors){
            console.log("Rejected by first promise1");
            let message = errors && Array.isArray(errors) && errors.length > 0 ? errors[0].message :'Unknown error';
            helper.showToastMessage(component, "Failure " + message);
        })

    ).then(
                $A.getCallback(function(){
                    console.log("Success");
                    helper.showToastMessage(component, "Great Success!!!!")
                }), 
                $A.getCallback(function(errorMessage){
                    console.log("Rejected by second promise2");
                    helper.showToastMessage(component, "Failure " + errorMessage);
                })

    )
}

However, if we need component and helper variables in the second promise then we have to tweak resolve statement and change it from

resolve(record)

to

resolve({'c':component, 'h':helper, 'r':record})

and to tweak the second promise from

sendMessagePromise : function(component, helper, record) {
    ...

to

sendMessagePromise : function(value) {
    let component = value.c, helper = value.h, record = value.r;

Also using suggestions from sfdcfox I could rewrite this as following:

main: function(component, helper) {
    helper.promise1(component, helper).then(
        promise2
    ).then(
        () => helper.showToastMessage(component, "Great Success!!!!")
    ).catch(
        errorMessage => helper.showToastMessage(component, "Failure " + errorMessage)
    );
}

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.