JS Promises with D365 Web APIs

JS Promises with D365 Web APIs

If you are a Dynamics CE Developer, definitely you would have worked on JS and there are times when you have to use D365 Web APIs, which as we all know are asynchronous in nature.

Let's take an example for our understanding. We have an entity called Scholarship having a Contact lookup labelled as Portal Contact. There's a scenario wherein we have to show a ribbon button in Scholarship Records, only if the below conditions are met otherwise the button will remain hidden:

  1. Gender of Portal Contact is Female

  2. Logged-in CRM User's Gender is Female

  3. Gender of Manager of Logged-in User is Female

Assuming we are doing it in a very naive way, to get the above requirement done, we will need couple of retrievals which we have to do via async retrieveRecord API. So, you got 2 ways to implement this:

  1. Nested Callbacks aka Hell of Callbacks!

  2. Promises

What do we say to the hell of callbacks?
Not Today!
*

*condition you know how to use promise

Why to use a Promise?

Come on, don't you know, it's synchronously asynchronous and pretty easy to use.

So what is a Promise?

  • Say we have a piece of asynchronous code that does something and takes time, let's call it producing code. In our case, D365 Web APIs, which are async in nature.

  • And we have another piece of code(s) which needs the result of the producing code once it’s ready to perform certain dependent actions, let's call it consuming code.

  • A promise is a special JavaScript object which links the producing code and the consuming code together. The producing code provides the promised result in the future, and the promise makes that result available to consuming code when it’s ready.

  • A promise may be in one of 3 possible states: fulfilled, rejected or pending.

Basic Promise structure:

var promise = await new Promise(function(resolve, reject) {
  // execute your producing code here
});

Leveraging Promise with Dynamics 365 WebAPI

/*
Note: The function inside which await keyword
is used needs to marked an async function
*/
var sQuery = "?$select=fullname,adv_gender&$expand=parentsystemuserid($select=fullname,adv_gender)";
var objUserResult = await new Promise(function (resolve, reject) {
    Xrm.WebApi.retrieveRecord("systemuser", 
    "ee8d2b82-0000-a1a1-b2b2-c3c3d4d4e5e5", sQuery).then(
        function success(objResult) {
            // We will call resolve to notify that the Promise is fulfilled
            // objResult is returned along with resolve
            resolve(objResult); 
        },
        function (ex) {
            // We will call reject to notify that the Promise is rejected
            reject(ex.message || ex);
        }
    );
});
// The properties and data of objResult from the success callback
// of retrieveRecord is returned and stored in objUserResult
// This objUserResult can be used exactly in the same way we would
// have used the variable objResult in success callback
console.log(objUserResult);
console.log(objUserResult["fullname"]);
console.log(objUserResult["adv_gender"]);
console.log(objUserResult["parentsystemuserid"]["fullname"]);
console.log(objUserResult["parentsystemuserid"]["adv_gender"]);

The above piece of code is a basic example of how we can use Promise with Dynamics 365 WebAPI.

Let us implement the use case of ribbon button hide/show which is mentioned above using Promise and D365 Web API.

Get the below JS as Web Resource in CRM and just call the function HideShowApproveButton as Custom Enable Rule in the format RK.XRM.WebResource.Scholarship.HideShowApproveButton. Also, make sure to pass the CRM Parameter PrimaryControl with this function call.

Note how the function HideShowApproveButton in the below implementation is marked as async because await keyword is used inside it.

"use strict";
if (typeof (RK) === "undefined") { var RK = { __namespace: true }; }

if (typeof (RK.XRM) === "undefined") { RK.XRM = { __namespace: true }; }

if (typeof (RK.XRM.WebResource) === "undefined") { RK.XRM.WebResource = { __namespace: true }; }

RK.XRM.WebResource.Scholarship = {
    /**
     * Function Call Format --> RK.XRM.WebResource.Scholarship.HideShowApproveButton
     * @param {any} objPrimaryControl
     */
    HideShowApproveButton: async function (objPrimaryControl) {
        var objFormContext = objPrimaryControl;
        var objPortalContact = objFormContext.getAttribute("adv_portalcontactid");
        var sLoggedInUserId = Xrm.Utility.getGlobalContext().userSettings.userId;
        var iContactGenderCodeFemale = 2;
        var iUserGenderCodeFemale = 756250001;
        var bShowButton = false;
        try {
            if (RK.XRM.WebResource.Scholarship.IsValid(objPortalContact) &&
                RK.XRM.WebResource.Scholarship.IsValid(objPortalContact.getValue())) {
                var vContactGenderCode =
                    await RK.XRM.WebResource.Scholarship.GetPortalContactGenderCode(objPortalContact.getValue()[0].id);
                if (vContactGenderCode === iContactGenderCodeFemale) {
                    var objUser =
                        await RK.XRM.WebResource.Scholarship.GetUserAndManagerGenderCode(sLoggedInUserId);
                    if (RK.XRM.WebResource.Scholarship.IsValid(objUser) &&
                        RK.XRM.WebResource.Scholarship.IsValid(objUser.UserGender) &&
                        RK.XRM.WebResource.Scholarship.IsValid(objUser.ManagerGender) &&
                        (objUser.UserGender === iUserGenderCodeFemale) &&
                        (objUser.ManagerGender === iUserGenderCodeFemale)) {
                        bShowButton = true;
                    }
                }
            }
            return bShowButton;
        }
        catch (ex) {
            RK.XRM.WebResource.Scholarship.ShowAlert(ex.message || ex);
        }
    },

    /**
     * Function Call Format --> RK.XRM.WebResource.Scholarship.GetUserAndManagerGenderCode
     * @param {any} sUserId
     */
    GetUserAndManagerGenderCode: function (sUserId) {
        var objUser = {};
        objUser.UserGender = -1;
        objUser.ManagerGender = -1;
        try {
            return new Promise(function (resolve, reject) {
                Xrm.WebApi.retrieveRecord("systemuser", sUserId.replace("{", "").replace("}", ""), "?$select=fullname,adv_gender&$expand=parentsystemuserid($select=fullname,adv_gender)").then(
                    function success(objUserResult) {
                        if (RK.XRM.WebResource.Scholarship.IsValid(objUserResult)) {
                            if (RK.XRM.WebResource.Scholarship.IsValid(objUserResult["adv_gender"])) {
                                objUser.UserGender = objUserResult["adv_gender"];
                            }
                            if (RK.XRM.WebResource.Scholarship.IsValid(objUserResult["parentsystemuserid"]) &&
                                RK.XRM.WebResource.Scholarship.IsValid(objUserResult["parentsystemuserid"]["adv_gender"])) {
                                objUser.ManagerGender = objUserResult["parentsystemuserid"]["adv_gender"];
                            }
                        }
                        //RK.XRM.WebResource.Scholarship.ShowAlert("objUser.User :: " + objUser.UserGender);
                        //RK.XRM.WebResource.Scholarship.ShowAlert("objUser.Manager :: " + objUser.ManagerGender);
                        resolve(objUser);
                    },
                    function (ex) {
                        reject(ex.message || ex);
                    }
                );
            });
        }
        catch (ex) {
            RK.XRM.WebResource.Scholarship.ShowAlert(ex.message || ex);
        }
    },

    /**
     * Function Call Format --> RK.XRM.WebResource.Scholarship.GetPortalContactGenderCode
     * @param {any} sContactId
     */
    GetPortalContactGenderCode: function (sContactId) {
        var iContactGenderCode = -1;
        try {
            return new Promise(function (resolve, reject) {
                Xrm.WebApi.retrieveRecord("contact", sContactId.replace("{", "").replace("}", ""), "?$select=fullname,gendercode,contactid").then(
                    function success(objContactResult) {
                        if (RK.XRM.WebResource.Scholarship.IsValid(objContactResult) &&
                            RK.XRM.WebResource.Scholarship.IsValid(objContactResult["gendercode"])) {
                            iContactGenderCode = objContactResult["gendercode"];
                        }
                        //RK.XRM.WebResource.Scholarship.ShowAlert("iContactGenderCode :: " + iContactGenderCode);
                        resolve(iContactGenderCode);
                    },
                    function (ex) {
                        reject(ex.message || ex);
                    }
                );
            });
        }
        catch (ex) {
            RK.XRM.WebResource.Scholarship.ShowAlert(ex.message || ex);
        }
    },

    /**
     * Function Call Format --> RK.XRM.WebResource.Scholarship.IsValid
     * @param {any} object
     */
    IsValid: function (object) {
        try {
            if (object != null && object !== "undefined" && object !== undefined && object !== "") {
                return true;
            }
            else {
                return false;
            }
        }
        catch (ex) {
            RK.XRM.WebResource.Scholarship.ShowAlert(ex.message);
        }
    },

    /**
     * Function Call Format --> RK.XRM.WebResource.Scholarship.ShowAlert
     * @param {any} sErrMessage
     */
    ShowAlert: function (sErrMessage) {
        var objAlertStrings = null;
        var objAlertOptions = null;
        objAlertStrings = { confirmButtonLabel: "OK", text: sErrMessage };
        objAlertOptions = { height: 120, width: 260 };
        Xrm.Navigation.openAlertDialog(objAlertStrings, objAlertOptions).then(
            function success(result) {
            },
            function (ex) {
                alert(ex.message);
            }
        );
    },

    __namespace: true
};

Happy CRM-ing! Ciao.