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?

2

2 Answers 2

18

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).

4
  • Is that possible to use arrow functions in Lightning?
    – Patlatus
    Jan 12, 2018 at 17:38
  • @Patlatus Yes, you can use arrow functions in Lightning.
    – sfdcfox
    Jan 12, 2018 at 17:40
  • @sfdcfox how are you using arrow functions, I have not seen them work in Lightning? Mar 6, 2018 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, 2018 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)
    );
}

You must log in to answer this question.

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