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:
-
The classic way using
new Promise()and.then() -
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