" MicromOne: Handling Asynchronous Logic in Dynamics 365 Using Promises and Async/Await

Pagine

Handling Asynchronous Logic in Dynamics 365 Using Promises and Async/Await

When building custom client-side scripts in Microsoft Dynamics 365 / Power Apps, you’ll often need to make asynchronous Web API calls — for example, to validate data before saving a record.

This guide explains how to handle asynchronous logic using both the classic Promise syntax (.then()) and the modern async/await approach, with clean, adaptable examples for your own entities.

Scenario: Validate Data Before Save

Suppose you need to check whether a record already exists before saving.
You can call a custom action or Web API endpoint to perform that validation and decide whether to continue with the save.

We’ll look at two approaches:

  1. The classic way using new Promise() and .then()

  2. The modern way using async/await

Classic Approach — Using new Promise() and .then()

CheckRecord: function (formContext, callback) {
    return new Promise((resolve, reject) => {
        // Example: read values from the form
        var fieldA = formContext.getAttribute("fieldA")?.getValue() || "";
        var fieldB = formContext.getAttribute("fieldB")?.getValue() || "";

        // Build the request for your custom action
        var request = {
            CustomAction_RecordData: {
                "@odata.type": "Microsoft.Dynamics.CRM.sampleentity",
                "fieldA": fieldA,
                "fieldB": fieldB
            },
            getMetadata: function () {
                return {
                    boundParameter: null,
                    parameterTypes: {
                        CustomAction_RecordData: { typeName: "mscrm.sampleentity", structuralProperty: 5 }
                    },
                    operationType: 0,
                    operationName: "new_CustomAction"
                };
            }
        };

        // Execute the Web API call
        Xrm.WebApi.execute(request).then(async response => {
            const contentType = response.headers.get("content-type");
            if (contentType && contentType.includes("application/json")) {
                const jsonResponse = await response.json();
                if (jsonResponse && callback) {
                    callback();
                    resolve(true); // duplicate found
                }
            } else {
                resolve(false); // no duplicate
            }
        }).catch(error => {
            console.error("Error in CheckRecord:", error);
            reject(error);
        });
    });
},

Why This Approach Is Verbose

  • You must manually create a new Promise().

  • You need to chain .then() and .catch().

  • Error handling and readability can become messy with nested logic.

Modern Approach — Using async and await

CheckRecord: async function (formContext, callback) {
    try {
        // Read values from the form
        const fieldA = formContext.getAttribute("fieldA")?.getValue() || "";
        const fieldB = formContext.getAttribute("fieldB")?.getValue() || "";

        // Build the request
        const request = {
            CustomAction_RecordData: {
                "@odata.type": "Microsoft.Dynamics.CRM.sampleentity",
                "fieldA": fieldA,
                "fieldB": fieldB
            },
            getMetadata: function () {
                return {
                    boundParameter: null,
                    parameterTypes: {
                        CustomAction_RecordData: { typeName: "mscrm.sampleentity", structuralProperty: 5 }
                    },
                    operationType: 0,
                    operationName: "new_CustomAction"
                };
            }
        };

        // Await the Web API response
        const response = await Xrm.WebApi.execute(request);
        const contentType = response.headers.get("content-type");

        if (contentType && contentType.includes("application/json")) {
            const jsonResponse = await response.json();
            if (jsonResponse && callback) {
                callback();
                return true; // duplicate found
            }
        }

        return false; // no duplicate found
    } catch (error) {
        console.error("Error in CheckRecord:", error);
        return null; // error case
    }
},

Comparison: Classic vs Modern

Promise creation Manual (new Promise()) Automatic (async handles it)
Error handling .catch() try...catch
Code style Nested and verbose Linear and clean
Readability Harder to follow Easy, top-to-bottom flow

Using It in the Save Event

Example with .then()

MyNamespace.CheckRecord(formContext, () => {
    // handle duplicate
}).then(result => {
    if (result === false) {
        formContext.data.save(); // proceed with save
    }
});

Example with await

const result = await MyNamespace.CheckRecord(formContext, callback);
if (result === false) {
    formContext.data.save();
}

When to Still Use new Promise()

You only need to use new Promise() when working with APIs that do not return a Promise — for example:

function delay(ms) {
    return new Promise(resolve => setTimeout(resolve, ms));
}

In most Dynamics 365 Web API scenarios, Xrm.WebApi.execute() already returns a Promise, so using async/await is cleaner and safer.

Best Practices

  • Every Web API call in Dynamics 365 is asynchronous

  • Use async/await for simpler, cleaner syntax

  • Use new Promise() only with legacy or callback-based APIs

  • Keeping code linear and readable improves debugging, maintenance, and the user experience