Enforce request completion order using Multiple Promise Chaining

The name of the pictureThe name of the pictureThe name of the pictureClash Royale CLAN TAG#URR8PPP





.everyoneloves__top-leaderboard:empty,.everyoneloves__mid-leaderboard:empty margin-bottom:0;







up vote
3
down vote

favorite












The main entry point is the SyncmyCompany() function.



I have a interesting scenario where I'm syncing data from a Company to a destination system. In this case Dynamics 365 is the destination system. The main flow of the code is:



  1. Get the configuration defined in Dynamics 365 and store data in my configuration object.

  2. Only once complete step 1, pass in company user creds to get a sts token I used in the company's api calls and store it in my configuration object.

  3. Only once complete step 2, get task from the company api.

  4. Only once complete step 3, get task that exists in Dynamics 365 if any.

  5. Only once complete step 4, if there are task in Dynamics 365 delete them using the task id and Xrm WebApi helper delete method. This requires some behind the scenes deletion of sdk message and task trigger dependencies too.

  6. Only once complete step 5, use the Xrm WebApi helper create method to repopulate the dropped task data with new data from the source company.

I am using Promises in order to make this possible.



What changes can I make to my Promise logic to make this cleaner and have a stronger order enforced in my code?



Code



"use strict";

var MyCompanyDynamicsClientApi = MyCompanyDynamicsClientApi || ;

MyCompanyDynamicsClientApi.configuration =
clientUrl: "",
MyCompanyApiUrl: "",
MyCompanyChallengeHostEmail: "",
MyCompanyChallengeHostPassword: "",
MyCompanyChallengeId: "",
MyCompanyChallengeTasks: ,
MyCompanyChallengeTasksInDynamics: ,
MyCompanyClientSecret: "",
MyCompanyUsername: "",
MyCompanyPassword: "",
stsToken: ""


MyCompanyDynamicsClientApi.runSyncMyCompanyProcess = function()


Xrm.Utility.showProgressIndicator("Syncing MyCompany Data...");
MyCompanyDynamicsClientApi.retrieveMyCompanyConfiguration().then(function ()
return MyCompanyDynamicsClientApi.getMyCompanyTokenAsync();
).then(function ()
return MyCompanyDynamicsClientApi.getMyCompanyTasksAsync();
).then(function ()
return MyCompanyDynamicsClientApi.getMyCompanyTasksInDynamicsAsync();
).then(function ()
return MyCompanyDynamicsClientApi.deleteMyCompanyTasksAsync();
).then(function ()
return MyCompanyDynamicsClientApi.createMyCompanyTasksAsync();
).then(function ()
return MyCompanyDynamicsClientApi.openSyncMyCompanyCompleteDialog();
).catch(function (error)
return MyCompanyDynamicsClientApi.openSyncMyCompanyFailedDialog(error());
);


MyCompanyDynamicsClientApi.retrieveMyCompanyConfiguration = function()
console.log("Trying Promise for MyCompanyConfiguration");
return new Promise(function(resolve, reject)
MyCompanyDynamicsClientApi.configuration.clientUrl = window.parent.Xrm.Utility.getGlobalContext().getClientUrl();

var MyCompanyConfigurationFetchXml = '<fetch version="1.0" output-format="xml-platform" mapping="logical" distinct="true" no-lock="false">';
MyCompanyConfigurationFetchXml = MyCompanyConfigurationFetchXml.concat(
'<entity name="MyCompany_MyCompanyconfiguration">',
'<attribute name="MyCompany_apiurl" />',
'<attribute name="MyCompany_challengehostemail" />',
'<attribute name="MyCompany_challengehostpassword" />',
'<attribute name="MyCompany_clientsecret" />',
'<attribute name="MyCompany_MyCompanyconfigurationid" />',
'<filter type="and">',
'<condition attribute="MyCompany_apiurl" operator="not-null" />',
'<condition attribute="MyCompany_challengehostemail" operator="not-null" />',
'<condition attribute="MyCompany_challengehostpassword" operator="not-null" />',
'<condition attribute="MyCompany_clientsecret" operator="not-null" />',
'<condition attribute="statecode" operator="eq" value="0" />',
'</filter>',
'<link-entity name="MyCompany_MyCompanychallenge" from="MyCompany_MyCompanyconfigurationid" to="MyCompany_MyCompanyconfigurationid" link-type="inner" alias="MyCompany_MyCompanychallenge">',
'<attribute name="MyCompany_MyCompanyid" />',
'<attribute name="MyCompany_name" />',
'<attribute name="MyCompany_MyCompanyconfigurationid" />',
'<filter type="and">',
'<condition attribute="MyCompany_MyCompanyid" operator="not-null" />',
'<condition attribute="MyCompany_name" operator="not-null" />',
'<condition attribute="MyCompany_MyCompanyconfigurationid" operator="not-null" />',
'<condition attribute="statecode" operator="eq" value="0" />',
'</filter>',
'</link-entity>',
'</entity>',
'</fetch>'
);
var MyCompanyConfigurationFetchXmlEncoded = encodeURI(MyCompanyConfigurationFetchXml);
var MyCompanyConfigurationRequestPath = "";
MyCompanyConfigurationRequestPath = MyCompanyConfigurationRequestPath.concat(
MyCompanyDynamicsClientApi.configuration.clientUrl,
"/api/data/v9.0/MyCompany_MyCompanyconfigurations?fetchXml=",
MyCompanyConfigurationFetchXmlEncoded
);

MyCompanyDynamicsClientApi.request("GET", MyCompanyConfigurationRequestPath, "application/json").then(function (response)
try
MyCompanyDynamicsClientApi.configuration.MyCompanyApiUrl = response.value[0].MyCompany_apiurl.toString();
MyCompanyDynamicsClientApi.configuration.MyCompanyChallengeHostEmail = response.value[0].MyCompany_challengehostemail.toString();
MyCompanyDynamicsClientApi.configuration.MyCompanyChallengeHostPassword = response.value[0].MyCompany_challengehostpassword.toString();
MyCompanyDynamicsClientApi.configuration.MyCompanyChallengeId = response.value[0]["MyCompany_MyCompanychallenge.MyCompany_MyCompanyid"].toString();
MyCompanyDynamicsClientApi.configuration.MyCompanyClientSecret = response.value[0].MyCompany_clientsecret.toString();
resolve();
catch (error) reject(new Error("Failed Promise MyCompanyDynamicsClientApi.retrieveMyCompanyConfiguration" + error.message));
).catch(function(error) reject(new Error("Failed Promise MyCompanyDynamicsClientApi.retrieveMyCompanyConfiguration" + error.message)); );
);

MyCompanyDynamicsClientApi.getMyCompanyTokenAsync = function()
console.log("Trying Promise for MyCompanyTokenAsync");
return new Promise(function(resolve, reject)

var stsToken = "";
var tokenRequestParams = "grant_type=password&scope=MyCompanyApi&username=" + MyCompanyDynamicsClientApi.configuration.MyCompanyChallengeHostEmail + "&password=" + MyCompanyDynamicsClientApi.configuration.MyCompanyChallengeHostPassword;
var tokenRequestHeaders = "Authorization": "Basic TWljcm9zb2Z0RmxvdzI6W0k0JjUoXVtPZUJsTk4laGduIXo5cH1TMmp5Wlt4Xmk5" ;
var stsTokenRequestUri = "https://playMyCompanydev.azurewebsites.net/sts/connect/token";
MyCompanyDynamicsClientApi.request("POST", stsTokenRequestUri, "application/x-www-form-urlencoded", tokenRequestHeaders, tokenRequestParams).then(function (response)
try
MyCompanyDynamicsClientApi.configuration.stsToken = response.access_token.toString();
resolve();
catch (error) reject(new Error("Failed Promise MyCompanyDynamicsClientApi.getMyCompanyTokenAsync" + error.message));
).catch(function(error) reject(new Error("Failed Promise MyCompanyDynamicsClientApi.getMyCompanyTokenAsync" + error.message)); );
);

MyCompanyDynamicsClientApi.getMyCompanyTasksAsync = function()
console.log("Trying Promise for getMyCompanyTasksAsync");
return new Promise(function(resolve, reject)

var taskRequestHeaders = "Authorization": "Bearer " + MyCompanyDynamicsClientApi.configuration.stsToken ;
var taskRequestUri = MyCompanyDynamicsClientApi.configuration.MyCompanyApiUrl + "dynamics/" + MyCompanyDynamicsClientApi.configuration.MyCompanyChallengeId + "/tasks";
MyCompanyDynamicsClientApi.request("GET", taskRequestUri, "application/json", taskRequestHeaders).then(function (response)
try
MyCompanyDynamicsClientApi.configuration.MyCompanyChallengeTasks = response;
resolve();
catch (error) reject(new Error("Failed Promise MyCompanyDynamicsClientApi.getMyCompanyTasksAsync" + error.message));
).catch(function(error) reject(new Error("Failed Promise MyCompanyDynamicsClientApi.getMyCompanyTasksAsync" + error.message)); );
);

MyCompanyDynamicsClientApi.getMyCompanyTasksInDynamicsAsync = function()
console.log("Trying Promise for getMyCompanyTasksInDynamicsAsync");
return new Promise(function(resolve, reject)

var MyCompanyTasksFetchXml = '<fetch version="1.0" output-format="xml-platform" mapping="logical" distinct="true" no-lock="false">';
MyCompanyTasksFetchXml = MyCompanyTasksFetchXml.concat(
'<entity name="MyCompany_MyCompanytask">',
'<attribute name="MyCompany_MyCompanytaskid" />',
'<attribute name="MyCompany_MyCompanytaskidreference" />',
'<attribute name="MyCompany_sdkmessagestepid" />',
'<filter type="and">',
'<condition attribute="MyCompany_MyCompanytaskid" operator="not-null" />',
'<condition attribute="MyCompany_MyCompanytaskidreference" operator="not-null" />',
'<condition attribute="MyCompany_sdkmessagestepid" operator="not-null" />',
'</filter>',
'<link-entity name="MyCompany_MyCompanytrigger" from="MyCompany_MyCompanytriggerid" to="MyCompany_MyCompanytriggerid" link-type="inner" alias="MyCompany_MyCompanytrigger" >',
'<attribute name="MyCompany_MyCompanytriggerid" />',
'<filter type="and">',
'<condition attribute="MyCompany_MyCompanytriggerid" operator="not-null" />',
'</filter>',
'</link-entity>',
'</entity>',
'</fetch>'
);
// TODO: Add filter to fetch xml to merge task instead of just drop and recreate table
// '<condition attribute="MyCompany_apiurl" operator="not-null" />',
// '<filter type="and">',
// '<condition attribute="MyCompany_MyCompanytaskidreference" operator="eq" value="' + MyCompanyTaskIdReference + '" />',
// '</filter>',
var MyCompanyTasksFetchXmlEncoded = encodeURI(MyCompanyTasksFetchXml);
var MyCompanyTasksRequestPath = "";
MyCompanyTasksRequestPath = MyCompanyTasksRequestPath.concat(
MyCompanyDynamicsClientApi.configuration.clientUrl,
"/api/data/v9.0/MyCompany_MyCompanytasks?fetchXml=",
MyCompanyTasksFetchXmlEncoded
);
MyCompanyDynamicsClientApi.request("GET", MyCompanyTasksRequestPath, "application/json").then(function (response)
try
MyCompanyDynamicsClientApi.configuration.MyCompanyChallengeTasksInDynamics = response;
resolve();
catch (error)
).catch(function(error) );
);

MyCompanyDynamicsClientApi.deleteMyCompanyTasksAsync = function()
console.log("Trying Promise for deleteMyCompanyTasksAsync");
return new Promise(function(resolve, reject)

var deleteSdkMessage = function (sdkmessagestepid)
return new Promise(function(resolve, reject)
if (sdkmessagestepid)
Xrm.WebApi.deleteRecord("sdkmessageprocessingstep", sdkmessagestepid).then(
function success(result)
console.log("Deleted sdkmessageprocessingstep " + result);
resolve();
,
function (error) console.log("ERROR: Xrm.WebApi.deleteRecord " + error.message.toString());
).catch(function(error)
reject(new Error("Failed Promise MyCompanyDynamicsClientApi.deleteMyCompanyTasksAsync " + error.message));
);
else
resolve();

);
;

var deleteMyCompanyTrigger = function (triggerid)
return new Promise(function(resolve, reject)
if (triggerid)
Xrm.WebApi.deleteRecord("MyCompany_MyCompanytrigger", triggerid).then(
function success(result)
console.log("Deleted MyCompany_MyCompanytrigger " + result);
resolve();
,
function (error) console.log("ERROR: Xrm.WebApi.deleteRecord " + error.message.toString());
).catch(function(error)
reject(new Error("Failed Promise MyCompanyDynamicsClientApi.deleteMyCompanyTasksAsync " + error.message));
);
else
resolve();

);
;

var deleteMyCompanyTask = function (taskid)
return new Promise(function(resolve, reject)
if (taskid)
Xrm.WebApi.deleteRecord("MyCompany_MyCompanytask", taskid).then(
function success(result)
console.log("Deleted MyCompany_MyCompanytask " + result);
resolve();
,
function (error) console.log("ERROR: Xrm.WebApi.deleteRecord " + error.message.toString());
).catch(function(error)
reject(new Error("Failed Promise MyCompanyDynamicsClientApi.deleteMyCompanyTasksAsync " + error.message));
);
else
resolve();

);
;

var deleteMyCompanyTasksAsyncPromises = ;
var MyCompanytaskid = "";
var MyCompanysdkmessagestepid = "";
var MyCompanytriggerid = "";
for (var i = 0; i < MyCompanyDynamicsClientApi.configuration.MyCompanyChallengeTasksInDynamics.value.length; i++)

if (MyCompanyDynamicsClientApi.configuration.MyCompanyChallengeTasksInDynamics.value[i])
MyCompanysdkmessagestepid = MyCompanyDynamicsClientApi.configuration.MyCompanyChallengeTasksInDynamics.value[i].MyCompany_sdkmessagestepid.toString();
deleteMyCompanyTasksAsyncPromises.push( deleteSdkMessage(MyCompanysdkmessagestepid) );

if (MyCompanyDynamicsClientApi.configuration.MyCompanyChallengeTasksInDynamics.value[i]["MyCompany_MyCompanytrigger.MyCompany_MyCompanytriggerid"])
MyCompanytriggerid = MyCompanyDynamicsClientApi.configuration.MyCompanyChallengeTasksInDynamics.value[i]["MyCompany_MyCompanytrigger.MyCompany_MyCompanytriggerid"].toString();
deleteMyCompanyTasksAsyncPromises.push( deleteMyCompanyTrigger(MyCompanytriggerid) );

if (MyCompanyDynamicsClientApi.configuration.MyCompanyChallengeTasksInDynamics.value[i].MyCompany_MyCompanytaskid)
MyCompanytaskid = MyCompanyDynamicsClientApi.configuration.MyCompanyChallengeTasksInDynamics.value[i].MyCompany_MyCompanytaskid.toString();
deleteMyCompanyTasksAsyncPromises.push( deleteMyCompanyTask(MyCompanytaskid) );


Promise.all(deleteMyCompanyTasksAsyncPromises).then(function()
resolve();
);
);

MyCompanyDynamicsClientApi.createMyCompanyTasksAsync = function()
console.log("Trying Promise for createMyCompanyTasksAsync");
return new Promise(function(resolve, reject)

var createMyCompanyTask = function (trigger)
return new Promise(function(resolve, reject)
if (trigger)
Xrm.WebApi.createRecord("MyCompany_MyCompanytrigger", trigger).then(
function success(result)
console.log("Created task and attached trigger to it, trigger is " + MyCompanyTrigger);
resolve();
,
function (error)
console.log("ERROR: Xrm.WebApi.createRecord " + error.message.toString());

).catch(function(error)
reject(new Error("Failed Promise MyCompanyDynamicsClientApi.createMyCompanyTasksAsync" + error.message));
);
else
resolve();

);
;

var createMyCompanyTasksAsyncPromises = ;
var MyCompanyTrigger = ;
var MyCompanyEntityName = "";
for (var i = 0; i < MyCompanyDynamicsClientApi.configuration.MyCompanyChallengeTasks.length; i++)

MyCompanyEntityName = MyCompanyDynamicsClientApi.configuration.MyCompanyChallengeTasks[i].primaryobjecttypecode[0].toUpperCase() + MyCompanyDynamicsClientApi.configuration.MyCompanyChallengeTasks[i].primaryobjecttypecode.slice(1);
MyCompanyTrigger =

"MyCompany_name": MyCompanyDynamicsClientApi.configuration.MyCompanyChallengeTasks[i].name + " " + MyCompanyEntityName,
"MyCompany_entityname": MyCompanyEntityName,
"MyCompany_messagename": MyCompanyDynamicsClientApi.configuration.MyCompanyChallengeTasks[i].name,
// MyCompany_MyCompany_MyCompanytask_MyCompany_MyCompanytrigger navigation property (1:N), from DataModel/MyCompanyCrmSdkTypes, generated class using the CrmSvcUtil.exe
"MyCompany_MyCompany_MyCompanytrigger_MyCompany_MyCompanytask_MyCompanytriggerid":
[

"MyCompany_name": MyCompanyDynamicsClientApi.configuration.MyCompanyChallengeTasks[i].Title,
"MyCompany_MyCompanytaskidreference": MyCompanyDynamicsClientApi.configuration.MyCompanyChallengeTasks[i].TaskId,
"MyCompany_enabled": true,
"MyCompany_pointvalue": MyCompanyDynamicsClientApi.configuration.MyCompanyChallengeTasks[i].EligiblePoints

]
;
createMyCompanyTasksAsyncPromises.push( createMyCompanyTask(MyCompanyTrigger) );


Promise.all(createMyCompanyTasksAsyncPromises).then(function()
resolve();
);
);


MyCompanyDynamicsClientApi.request = function(method, uri, contentType, otherHeaders, data)

if (!RegExp(method, "g").test("POST PATCH PUT GET DELETE"))
throw new Error("MyCompanyDynamicsClientApi.request: method must be: " +
"POST, PATCH, PUT, GET, or DELETE.");

if (!typeof uri === "string"

MyCompanyDynamicsClientApi.openSyncMyCompanyConfirmDialog = function()

console.log("Opening confirm dialog...");
var confirmStrings =
cancelButtonLabel: "No",
confirmButtonLabel: "Yes",
title: "MyCompany Sync",
subtitle: "Are you sure you want to start a sync?"
;
var confirmOptions = height: 200, width: 450 ;

Xrm.Navigation.openConfirmDialog(confirmStrings, confirmOptions).then(
success =>
if (success.confirmed)
console.log("openSyncMyCompanyConfirmDialog confirmed.");
MyCompanyDynamicsClientApi.runSyncMyCompanyProcess();

else
console.log("openSyncMyCompanyConfirmDialog closed.");

,
error =>
console.log(error.message);
);

MyCompanyDynamicsClientApi.openSyncMyCompanyCompleteDialog = function()
return new Promise(function(resolve, reject)
Xrm.Utility.closeProgressIndicator();
var alertStrings =
confirmButtonLabel: "Close",
text: "Sync Complete! You can safely close this window."
;
var alertOptions = height: 120, width: 260 ;
Xrm.Navigation.openAlertDialog(alertStrings, alertOptions).then(
function success(result)
resolve();
console.log("openSyncMyCompanyCompleteDialog dialog closed");
,
function (error) console.log(error.message);
);
);

MyCompanyDynamicsClientApi.openSyncMyCompanyFailedDialog = function(error)
return new Promise(function(resolve, reject)
Xrm.Utility.closeProgressIndicator();
var alertStrings =
confirmButtonLabel: "Close",
text: "ERROR :( the sync failed... please contact your admin. Error details belown" + error.toString()
;
var alertOptions = height: 120, width: 260 ;
Xrm.Navigation.openAlertDialog(alertStrings, alertOptions).then(
function success(result)
resolve();
console.log("openSyncMyCompanyFailedDialog dialog closed");
,
function (error) console.log(error.message);
);
);


function SyncMyCompany()

console.log("SyncMyCompany running...");
MyCompanyDynamicsClientApi.openSyncMyCompanyConfirmDialog();







share|improve this question

















  • 1




    updated question to call this out and highlight review of the syntax and structure of code.
    – Greg Degruy
    Jun 8 at 18:47










  • This is better IMO. Thanks
    – Igor Soloydenko
    Jun 8 at 19:08










  • completed edits
    – Greg Degruy
    Jun 8 at 21:58
















up vote
3
down vote

favorite












The main entry point is the SyncmyCompany() function.



I have a interesting scenario where I'm syncing data from a Company to a destination system. In this case Dynamics 365 is the destination system. The main flow of the code is:



  1. Get the configuration defined in Dynamics 365 and store data in my configuration object.

  2. Only once complete step 1, pass in company user creds to get a sts token I used in the company's api calls and store it in my configuration object.

  3. Only once complete step 2, get task from the company api.

  4. Only once complete step 3, get task that exists in Dynamics 365 if any.

  5. Only once complete step 4, if there are task in Dynamics 365 delete them using the task id and Xrm WebApi helper delete method. This requires some behind the scenes deletion of sdk message and task trigger dependencies too.

  6. Only once complete step 5, use the Xrm WebApi helper create method to repopulate the dropped task data with new data from the source company.

I am using Promises in order to make this possible.



What changes can I make to my Promise logic to make this cleaner and have a stronger order enforced in my code?



Code



"use strict";

var MyCompanyDynamicsClientApi = MyCompanyDynamicsClientApi || ;

MyCompanyDynamicsClientApi.configuration =
clientUrl: "",
MyCompanyApiUrl: "",
MyCompanyChallengeHostEmail: "",
MyCompanyChallengeHostPassword: "",
MyCompanyChallengeId: "",
MyCompanyChallengeTasks: ,
MyCompanyChallengeTasksInDynamics: ,
MyCompanyClientSecret: "",
MyCompanyUsername: "",
MyCompanyPassword: "",
stsToken: ""


MyCompanyDynamicsClientApi.runSyncMyCompanyProcess = function()


Xrm.Utility.showProgressIndicator("Syncing MyCompany Data...");
MyCompanyDynamicsClientApi.retrieveMyCompanyConfiguration().then(function ()
return MyCompanyDynamicsClientApi.getMyCompanyTokenAsync();
).then(function ()
return MyCompanyDynamicsClientApi.getMyCompanyTasksAsync();
).then(function ()
return MyCompanyDynamicsClientApi.getMyCompanyTasksInDynamicsAsync();
).then(function ()
return MyCompanyDynamicsClientApi.deleteMyCompanyTasksAsync();
).then(function ()
return MyCompanyDynamicsClientApi.createMyCompanyTasksAsync();
).then(function ()
return MyCompanyDynamicsClientApi.openSyncMyCompanyCompleteDialog();
).catch(function (error)
return MyCompanyDynamicsClientApi.openSyncMyCompanyFailedDialog(error());
);


MyCompanyDynamicsClientApi.retrieveMyCompanyConfiguration = function()
console.log("Trying Promise for MyCompanyConfiguration");
return new Promise(function(resolve, reject)
MyCompanyDynamicsClientApi.configuration.clientUrl = window.parent.Xrm.Utility.getGlobalContext().getClientUrl();

var MyCompanyConfigurationFetchXml = '<fetch version="1.0" output-format="xml-platform" mapping="logical" distinct="true" no-lock="false">';
MyCompanyConfigurationFetchXml = MyCompanyConfigurationFetchXml.concat(
'<entity name="MyCompany_MyCompanyconfiguration">',
'<attribute name="MyCompany_apiurl" />',
'<attribute name="MyCompany_challengehostemail" />',
'<attribute name="MyCompany_challengehostpassword" />',
'<attribute name="MyCompany_clientsecret" />',
'<attribute name="MyCompany_MyCompanyconfigurationid" />',
'<filter type="and">',
'<condition attribute="MyCompany_apiurl" operator="not-null" />',
'<condition attribute="MyCompany_challengehostemail" operator="not-null" />',
'<condition attribute="MyCompany_challengehostpassword" operator="not-null" />',
'<condition attribute="MyCompany_clientsecret" operator="not-null" />',
'<condition attribute="statecode" operator="eq" value="0" />',
'</filter>',
'<link-entity name="MyCompany_MyCompanychallenge" from="MyCompany_MyCompanyconfigurationid" to="MyCompany_MyCompanyconfigurationid" link-type="inner" alias="MyCompany_MyCompanychallenge">',
'<attribute name="MyCompany_MyCompanyid" />',
'<attribute name="MyCompany_name" />',
'<attribute name="MyCompany_MyCompanyconfigurationid" />',
'<filter type="and">',
'<condition attribute="MyCompany_MyCompanyid" operator="not-null" />',
'<condition attribute="MyCompany_name" operator="not-null" />',
'<condition attribute="MyCompany_MyCompanyconfigurationid" operator="not-null" />',
'<condition attribute="statecode" operator="eq" value="0" />',
'</filter>',
'</link-entity>',
'</entity>',
'</fetch>'
);
var MyCompanyConfigurationFetchXmlEncoded = encodeURI(MyCompanyConfigurationFetchXml);
var MyCompanyConfigurationRequestPath = "";
MyCompanyConfigurationRequestPath = MyCompanyConfigurationRequestPath.concat(
MyCompanyDynamicsClientApi.configuration.clientUrl,
"/api/data/v9.0/MyCompany_MyCompanyconfigurations?fetchXml=",
MyCompanyConfigurationFetchXmlEncoded
);

MyCompanyDynamicsClientApi.request("GET", MyCompanyConfigurationRequestPath, "application/json").then(function (response)
try
MyCompanyDynamicsClientApi.configuration.MyCompanyApiUrl = response.value[0].MyCompany_apiurl.toString();
MyCompanyDynamicsClientApi.configuration.MyCompanyChallengeHostEmail = response.value[0].MyCompany_challengehostemail.toString();
MyCompanyDynamicsClientApi.configuration.MyCompanyChallengeHostPassword = response.value[0].MyCompany_challengehostpassword.toString();
MyCompanyDynamicsClientApi.configuration.MyCompanyChallengeId = response.value[0]["MyCompany_MyCompanychallenge.MyCompany_MyCompanyid"].toString();
MyCompanyDynamicsClientApi.configuration.MyCompanyClientSecret = response.value[0].MyCompany_clientsecret.toString();
resolve();
catch (error) reject(new Error("Failed Promise MyCompanyDynamicsClientApi.retrieveMyCompanyConfiguration" + error.message));
).catch(function(error) reject(new Error("Failed Promise MyCompanyDynamicsClientApi.retrieveMyCompanyConfiguration" + error.message)); );
);

MyCompanyDynamicsClientApi.getMyCompanyTokenAsync = function()
console.log("Trying Promise for MyCompanyTokenAsync");
return new Promise(function(resolve, reject)

var stsToken = "";
var tokenRequestParams = "grant_type=password&scope=MyCompanyApi&username=" + MyCompanyDynamicsClientApi.configuration.MyCompanyChallengeHostEmail + "&password=" + MyCompanyDynamicsClientApi.configuration.MyCompanyChallengeHostPassword;
var tokenRequestHeaders = "Authorization": "Basic TWljcm9zb2Z0RmxvdzI6W0k0JjUoXVtPZUJsTk4laGduIXo5cH1TMmp5Wlt4Xmk5" ;
var stsTokenRequestUri = "https://playMyCompanydev.azurewebsites.net/sts/connect/token";
MyCompanyDynamicsClientApi.request("POST", stsTokenRequestUri, "application/x-www-form-urlencoded", tokenRequestHeaders, tokenRequestParams).then(function (response)
try
MyCompanyDynamicsClientApi.configuration.stsToken = response.access_token.toString();
resolve();
catch (error) reject(new Error("Failed Promise MyCompanyDynamicsClientApi.getMyCompanyTokenAsync" + error.message));
).catch(function(error) reject(new Error("Failed Promise MyCompanyDynamicsClientApi.getMyCompanyTokenAsync" + error.message)); );
);

MyCompanyDynamicsClientApi.getMyCompanyTasksAsync = function()
console.log("Trying Promise for getMyCompanyTasksAsync");
return new Promise(function(resolve, reject)

var taskRequestHeaders = "Authorization": "Bearer " + MyCompanyDynamicsClientApi.configuration.stsToken ;
var taskRequestUri = MyCompanyDynamicsClientApi.configuration.MyCompanyApiUrl + "dynamics/" + MyCompanyDynamicsClientApi.configuration.MyCompanyChallengeId + "/tasks";
MyCompanyDynamicsClientApi.request("GET", taskRequestUri, "application/json", taskRequestHeaders).then(function (response)
try
MyCompanyDynamicsClientApi.configuration.MyCompanyChallengeTasks = response;
resolve();
catch (error) reject(new Error("Failed Promise MyCompanyDynamicsClientApi.getMyCompanyTasksAsync" + error.message));
).catch(function(error) reject(new Error("Failed Promise MyCompanyDynamicsClientApi.getMyCompanyTasksAsync" + error.message)); );
);

MyCompanyDynamicsClientApi.getMyCompanyTasksInDynamicsAsync = function()
console.log("Trying Promise for getMyCompanyTasksInDynamicsAsync");
return new Promise(function(resolve, reject)

var MyCompanyTasksFetchXml = '<fetch version="1.0" output-format="xml-platform" mapping="logical" distinct="true" no-lock="false">';
MyCompanyTasksFetchXml = MyCompanyTasksFetchXml.concat(
'<entity name="MyCompany_MyCompanytask">',
'<attribute name="MyCompany_MyCompanytaskid" />',
'<attribute name="MyCompany_MyCompanytaskidreference" />',
'<attribute name="MyCompany_sdkmessagestepid" />',
'<filter type="and">',
'<condition attribute="MyCompany_MyCompanytaskid" operator="not-null" />',
'<condition attribute="MyCompany_MyCompanytaskidreference" operator="not-null" />',
'<condition attribute="MyCompany_sdkmessagestepid" operator="not-null" />',
'</filter>',
'<link-entity name="MyCompany_MyCompanytrigger" from="MyCompany_MyCompanytriggerid" to="MyCompany_MyCompanytriggerid" link-type="inner" alias="MyCompany_MyCompanytrigger" >',
'<attribute name="MyCompany_MyCompanytriggerid" />',
'<filter type="and">',
'<condition attribute="MyCompany_MyCompanytriggerid" operator="not-null" />',
'</filter>',
'</link-entity>',
'</entity>',
'</fetch>'
);
// TODO: Add filter to fetch xml to merge task instead of just drop and recreate table
// '<condition attribute="MyCompany_apiurl" operator="not-null" />',
// '<filter type="and">',
// '<condition attribute="MyCompany_MyCompanytaskidreference" operator="eq" value="' + MyCompanyTaskIdReference + '" />',
// '</filter>',
var MyCompanyTasksFetchXmlEncoded = encodeURI(MyCompanyTasksFetchXml);
var MyCompanyTasksRequestPath = "";
MyCompanyTasksRequestPath = MyCompanyTasksRequestPath.concat(
MyCompanyDynamicsClientApi.configuration.clientUrl,
"/api/data/v9.0/MyCompany_MyCompanytasks?fetchXml=",
MyCompanyTasksFetchXmlEncoded
);
MyCompanyDynamicsClientApi.request("GET", MyCompanyTasksRequestPath, "application/json").then(function (response)
try
MyCompanyDynamicsClientApi.configuration.MyCompanyChallengeTasksInDynamics = response;
resolve();
catch (error)
).catch(function(error) );
);

MyCompanyDynamicsClientApi.deleteMyCompanyTasksAsync = function()
console.log("Trying Promise for deleteMyCompanyTasksAsync");
return new Promise(function(resolve, reject)

var deleteSdkMessage = function (sdkmessagestepid)
return new Promise(function(resolve, reject)
if (sdkmessagestepid)
Xrm.WebApi.deleteRecord("sdkmessageprocessingstep", sdkmessagestepid).then(
function success(result)
console.log("Deleted sdkmessageprocessingstep " + result);
resolve();
,
function (error) console.log("ERROR: Xrm.WebApi.deleteRecord " + error.message.toString());
).catch(function(error)
reject(new Error("Failed Promise MyCompanyDynamicsClientApi.deleteMyCompanyTasksAsync " + error.message));
);
else
resolve();

);
;

var deleteMyCompanyTrigger = function (triggerid)
return new Promise(function(resolve, reject)
if (triggerid)
Xrm.WebApi.deleteRecord("MyCompany_MyCompanytrigger", triggerid).then(
function success(result)
console.log("Deleted MyCompany_MyCompanytrigger " + result);
resolve();
,
function (error) console.log("ERROR: Xrm.WebApi.deleteRecord " + error.message.toString());
).catch(function(error)
reject(new Error("Failed Promise MyCompanyDynamicsClientApi.deleteMyCompanyTasksAsync " + error.message));
);
else
resolve();

);
;

var deleteMyCompanyTask = function (taskid)
return new Promise(function(resolve, reject)
if (taskid)
Xrm.WebApi.deleteRecord("MyCompany_MyCompanytask", taskid).then(
function success(result)
console.log("Deleted MyCompany_MyCompanytask " + result);
resolve();
,
function (error) console.log("ERROR: Xrm.WebApi.deleteRecord " + error.message.toString());
).catch(function(error)
reject(new Error("Failed Promise MyCompanyDynamicsClientApi.deleteMyCompanyTasksAsync " + error.message));
);
else
resolve();

);
;

var deleteMyCompanyTasksAsyncPromises = ;
var MyCompanytaskid = "";
var MyCompanysdkmessagestepid = "";
var MyCompanytriggerid = "";
for (var i = 0; i < MyCompanyDynamicsClientApi.configuration.MyCompanyChallengeTasksInDynamics.value.length; i++)

if (MyCompanyDynamicsClientApi.configuration.MyCompanyChallengeTasksInDynamics.value[i])
MyCompanysdkmessagestepid = MyCompanyDynamicsClientApi.configuration.MyCompanyChallengeTasksInDynamics.value[i].MyCompany_sdkmessagestepid.toString();
deleteMyCompanyTasksAsyncPromises.push( deleteSdkMessage(MyCompanysdkmessagestepid) );

if (MyCompanyDynamicsClientApi.configuration.MyCompanyChallengeTasksInDynamics.value[i]["MyCompany_MyCompanytrigger.MyCompany_MyCompanytriggerid"])
MyCompanytriggerid = MyCompanyDynamicsClientApi.configuration.MyCompanyChallengeTasksInDynamics.value[i]["MyCompany_MyCompanytrigger.MyCompany_MyCompanytriggerid"].toString();
deleteMyCompanyTasksAsyncPromises.push( deleteMyCompanyTrigger(MyCompanytriggerid) );

if (MyCompanyDynamicsClientApi.configuration.MyCompanyChallengeTasksInDynamics.value[i].MyCompany_MyCompanytaskid)
MyCompanytaskid = MyCompanyDynamicsClientApi.configuration.MyCompanyChallengeTasksInDynamics.value[i].MyCompany_MyCompanytaskid.toString();
deleteMyCompanyTasksAsyncPromises.push( deleteMyCompanyTask(MyCompanytaskid) );


Promise.all(deleteMyCompanyTasksAsyncPromises).then(function()
resolve();
);
);

MyCompanyDynamicsClientApi.createMyCompanyTasksAsync = function()
console.log("Trying Promise for createMyCompanyTasksAsync");
return new Promise(function(resolve, reject)

var createMyCompanyTask = function (trigger)
return new Promise(function(resolve, reject)
if (trigger)
Xrm.WebApi.createRecord("MyCompany_MyCompanytrigger", trigger).then(
function success(result)
console.log("Created task and attached trigger to it, trigger is " + MyCompanyTrigger);
resolve();
,
function (error)
console.log("ERROR: Xrm.WebApi.createRecord " + error.message.toString());

).catch(function(error)
reject(new Error("Failed Promise MyCompanyDynamicsClientApi.createMyCompanyTasksAsync" + error.message));
);
else
resolve();

);
;

var createMyCompanyTasksAsyncPromises = ;
var MyCompanyTrigger = ;
var MyCompanyEntityName = "";
for (var i = 0; i < MyCompanyDynamicsClientApi.configuration.MyCompanyChallengeTasks.length; i++)

MyCompanyEntityName = MyCompanyDynamicsClientApi.configuration.MyCompanyChallengeTasks[i].primaryobjecttypecode[0].toUpperCase() + MyCompanyDynamicsClientApi.configuration.MyCompanyChallengeTasks[i].primaryobjecttypecode.slice(1);
MyCompanyTrigger =

"MyCompany_name": MyCompanyDynamicsClientApi.configuration.MyCompanyChallengeTasks[i].name + " " + MyCompanyEntityName,
"MyCompany_entityname": MyCompanyEntityName,
"MyCompany_messagename": MyCompanyDynamicsClientApi.configuration.MyCompanyChallengeTasks[i].name,
// MyCompany_MyCompany_MyCompanytask_MyCompany_MyCompanytrigger navigation property (1:N), from DataModel/MyCompanyCrmSdkTypes, generated class using the CrmSvcUtil.exe
"MyCompany_MyCompany_MyCompanytrigger_MyCompany_MyCompanytask_MyCompanytriggerid":
[

"MyCompany_name": MyCompanyDynamicsClientApi.configuration.MyCompanyChallengeTasks[i].Title,
"MyCompany_MyCompanytaskidreference": MyCompanyDynamicsClientApi.configuration.MyCompanyChallengeTasks[i].TaskId,
"MyCompany_enabled": true,
"MyCompany_pointvalue": MyCompanyDynamicsClientApi.configuration.MyCompanyChallengeTasks[i].EligiblePoints

]
;
createMyCompanyTasksAsyncPromises.push( createMyCompanyTask(MyCompanyTrigger) );


Promise.all(createMyCompanyTasksAsyncPromises).then(function()
resolve();
);
);


MyCompanyDynamicsClientApi.request = function(method, uri, contentType, otherHeaders, data)

if (!RegExp(method, "g").test("POST PATCH PUT GET DELETE"))
throw new Error("MyCompanyDynamicsClientApi.request: method must be: " +
"POST, PATCH, PUT, GET, or DELETE.");

if (!typeof uri === "string"

MyCompanyDynamicsClientApi.openSyncMyCompanyConfirmDialog = function()

console.log("Opening confirm dialog...");
var confirmStrings =
cancelButtonLabel: "No",
confirmButtonLabel: "Yes",
title: "MyCompany Sync",
subtitle: "Are you sure you want to start a sync?"
;
var confirmOptions = height: 200, width: 450 ;

Xrm.Navigation.openConfirmDialog(confirmStrings, confirmOptions).then(
success =>
if (success.confirmed)
console.log("openSyncMyCompanyConfirmDialog confirmed.");
MyCompanyDynamicsClientApi.runSyncMyCompanyProcess();

else
console.log("openSyncMyCompanyConfirmDialog closed.");

,
error =>
console.log(error.message);
);

MyCompanyDynamicsClientApi.openSyncMyCompanyCompleteDialog = function()
return new Promise(function(resolve, reject)
Xrm.Utility.closeProgressIndicator();
var alertStrings =
confirmButtonLabel: "Close",
text: "Sync Complete! You can safely close this window."
;
var alertOptions = height: 120, width: 260 ;
Xrm.Navigation.openAlertDialog(alertStrings, alertOptions).then(
function success(result)
resolve();
console.log("openSyncMyCompanyCompleteDialog dialog closed");
,
function (error) console.log(error.message);
);
);

MyCompanyDynamicsClientApi.openSyncMyCompanyFailedDialog = function(error)
return new Promise(function(resolve, reject)
Xrm.Utility.closeProgressIndicator();
var alertStrings =
confirmButtonLabel: "Close",
text: "ERROR :( the sync failed... please contact your admin. Error details belown" + error.toString()
;
var alertOptions = height: 120, width: 260 ;
Xrm.Navigation.openAlertDialog(alertStrings, alertOptions).then(
function success(result)
resolve();
console.log("openSyncMyCompanyFailedDialog dialog closed");
,
function (error) console.log(error.message);
);
);


function SyncMyCompany()

console.log("SyncMyCompany running...");
MyCompanyDynamicsClientApi.openSyncMyCompanyConfirmDialog();







share|improve this question

















  • 1




    updated question to call this out and highlight review of the syntax and structure of code.
    – Greg Degruy
    Jun 8 at 18:47










  • This is better IMO. Thanks
    – Igor Soloydenko
    Jun 8 at 19:08










  • completed edits
    – Greg Degruy
    Jun 8 at 21:58












up vote
3
down vote

favorite









up vote
3
down vote

favorite











The main entry point is the SyncmyCompany() function.



I have a interesting scenario where I'm syncing data from a Company to a destination system. In this case Dynamics 365 is the destination system. The main flow of the code is:



  1. Get the configuration defined in Dynamics 365 and store data in my configuration object.

  2. Only once complete step 1, pass in company user creds to get a sts token I used in the company's api calls and store it in my configuration object.

  3. Only once complete step 2, get task from the company api.

  4. Only once complete step 3, get task that exists in Dynamics 365 if any.

  5. Only once complete step 4, if there are task in Dynamics 365 delete them using the task id and Xrm WebApi helper delete method. This requires some behind the scenes deletion of sdk message and task trigger dependencies too.

  6. Only once complete step 5, use the Xrm WebApi helper create method to repopulate the dropped task data with new data from the source company.

I am using Promises in order to make this possible.



What changes can I make to my Promise logic to make this cleaner and have a stronger order enforced in my code?



Code



"use strict";

var MyCompanyDynamicsClientApi = MyCompanyDynamicsClientApi || ;

MyCompanyDynamicsClientApi.configuration =
clientUrl: "",
MyCompanyApiUrl: "",
MyCompanyChallengeHostEmail: "",
MyCompanyChallengeHostPassword: "",
MyCompanyChallengeId: "",
MyCompanyChallengeTasks: ,
MyCompanyChallengeTasksInDynamics: ,
MyCompanyClientSecret: "",
MyCompanyUsername: "",
MyCompanyPassword: "",
stsToken: ""


MyCompanyDynamicsClientApi.runSyncMyCompanyProcess = function()


Xrm.Utility.showProgressIndicator("Syncing MyCompany Data...");
MyCompanyDynamicsClientApi.retrieveMyCompanyConfiguration().then(function ()
return MyCompanyDynamicsClientApi.getMyCompanyTokenAsync();
).then(function ()
return MyCompanyDynamicsClientApi.getMyCompanyTasksAsync();
).then(function ()
return MyCompanyDynamicsClientApi.getMyCompanyTasksInDynamicsAsync();
).then(function ()
return MyCompanyDynamicsClientApi.deleteMyCompanyTasksAsync();
).then(function ()
return MyCompanyDynamicsClientApi.createMyCompanyTasksAsync();
).then(function ()
return MyCompanyDynamicsClientApi.openSyncMyCompanyCompleteDialog();
).catch(function (error)
return MyCompanyDynamicsClientApi.openSyncMyCompanyFailedDialog(error());
);


MyCompanyDynamicsClientApi.retrieveMyCompanyConfiguration = function()
console.log("Trying Promise for MyCompanyConfiguration");
return new Promise(function(resolve, reject)
MyCompanyDynamicsClientApi.configuration.clientUrl = window.parent.Xrm.Utility.getGlobalContext().getClientUrl();

var MyCompanyConfigurationFetchXml = '<fetch version="1.0" output-format="xml-platform" mapping="logical" distinct="true" no-lock="false">';
MyCompanyConfigurationFetchXml = MyCompanyConfigurationFetchXml.concat(
'<entity name="MyCompany_MyCompanyconfiguration">',
'<attribute name="MyCompany_apiurl" />',
'<attribute name="MyCompany_challengehostemail" />',
'<attribute name="MyCompany_challengehostpassword" />',
'<attribute name="MyCompany_clientsecret" />',
'<attribute name="MyCompany_MyCompanyconfigurationid" />',
'<filter type="and">',
'<condition attribute="MyCompany_apiurl" operator="not-null" />',
'<condition attribute="MyCompany_challengehostemail" operator="not-null" />',
'<condition attribute="MyCompany_challengehostpassword" operator="not-null" />',
'<condition attribute="MyCompany_clientsecret" operator="not-null" />',
'<condition attribute="statecode" operator="eq" value="0" />',
'</filter>',
'<link-entity name="MyCompany_MyCompanychallenge" from="MyCompany_MyCompanyconfigurationid" to="MyCompany_MyCompanyconfigurationid" link-type="inner" alias="MyCompany_MyCompanychallenge">',
'<attribute name="MyCompany_MyCompanyid" />',
'<attribute name="MyCompany_name" />',
'<attribute name="MyCompany_MyCompanyconfigurationid" />',
'<filter type="and">',
'<condition attribute="MyCompany_MyCompanyid" operator="not-null" />',
'<condition attribute="MyCompany_name" operator="not-null" />',
'<condition attribute="MyCompany_MyCompanyconfigurationid" operator="not-null" />',
'<condition attribute="statecode" operator="eq" value="0" />',
'</filter>',
'</link-entity>',
'</entity>',
'</fetch>'
);
var MyCompanyConfigurationFetchXmlEncoded = encodeURI(MyCompanyConfigurationFetchXml);
var MyCompanyConfigurationRequestPath = "";
MyCompanyConfigurationRequestPath = MyCompanyConfigurationRequestPath.concat(
MyCompanyDynamicsClientApi.configuration.clientUrl,
"/api/data/v9.0/MyCompany_MyCompanyconfigurations?fetchXml=",
MyCompanyConfigurationFetchXmlEncoded
);

MyCompanyDynamicsClientApi.request("GET", MyCompanyConfigurationRequestPath, "application/json").then(function (response)
try
MyCompanyDynamicsClientApi.configuration.MyCompanyApiUrl = response.value[0].MyCompany_apiurl.toString();
MyCompanyDynamicsClientApi.configuration.MyCompanyChallengeHostEmail = response.value[0].MyCompany_challengehostemail.toString();
MyCompanyDynamicsClientApi.configuration.MyCompanyChallengeHostPassword = response.value[0].MyCompany_challengehostpassword.toString();
MyCompanyDynamicsClientApi.configuration.MyCompanyChallengeId = response.value[0]["MyCompany_MyCompanychallenge.MyCompany_MyCompanyid"].toString();
MyCompanyDynamicsClientApi.configuration.MyCompanyClientSecret = response.value[0].MyCompany_clientsecret.toString();
resolve();
catch (error) reject(new Error("Failed Promise MyCompanyDynamicsClientApi.retrieveMyCompanyConfiguration" + error.message));
).catch(function(error) reject(new Error("Failed Promise MyCompanyDynamicsClientApi.retrieveMyCompanyConfiguration" + error.message)); );
);

MyCompanyDynamicsClientApi.getMyCompanyTokenAsync = function()
console.log("Trying Promise for MyCompanyTokenAsync");
return new Promise(function(resolve, reject)

var stsToken = "";
var tokenRequestParams = "grant_type=password&scope=MyCompanyApi&username=" + MyCompanyDynamicsClientApi.configuration.MyCompanyChallengeHostEmail + "&password=" + MyCompanyDynamicsClientApi.configuration.MyCompanyChallengeHostPassword;
var tokenRequestHeaders = "Authorization": "Basic TWljcm9zb2Z0RmxvdzI6W0k0JjUoXVtPZUJsTk4laGduIXo5cH1TMmp5Wlt4Xmk5" ;
var stsTokenRequestUri = "https://playMyCompanydev.azurewebsites.net/sts/connect/token";
MyCompanyDynamicsClientApi.request("POST", stsTokenRequestUri, "application/x-www-form-urlencoded", tokenRequestHeaders, tokenRequestParams).then(function (response)
try
MyCompanyDynamicsClientApi.configuration.stsToken = response.access_token.toString();
resolve();
catch (error) reject(new Error("Failed Promise MyCompanyDynamicsClientApi.getMyCompanyTokenAsync" + error.message));
).catch(function(error) reject(new Error("Failed Promise MyCompanyDynamicsClientApi.getMyCompanyTokenAsync" + error.message)); );
);

MyCompanyDynamicsClientApi.getMyCompanyTasksAsync = function()
console.log("Trying Promise for getMyCompanyTasksAsync");
return new Promise(function(resolve, reject)

var taskRequestHeaders = "Authorization": "Bearer " + MyCompanyDynamicsClientApi.configuration.stsToken ;
var taskRequestUri = MyCompanyDynamicsClientApi.configuration.MyCompanyApiUrl + "dynamics/" + MyCompanyDynamicsClientApi.configuration.MyCompanyChallengeId + "/tasks";
MyCompanyDynamicsClientApi.request("GET", taskRequestUri, "application/json", taskRequestHeaders).then(function (response)
try
MyCompanyDynamicsClientApi.configuration.MyCompanyChallengeTasks = response;
resolve();
catch (error) reject(new Error("Failed Promise MyCompanyDynamicsClientApi.getMyCompanyTasksAsync" + error.message));
).catch(function(error) reject(new Error("Failed Promise MyCompanyDynamicsClientApi.getMyCompanyTasksAsync" + error.message)); );
);

MyCompanyDynamicsClientApi.getMyCompanyTasksInDynamicsAsync = function()
console.log("Trying Promise for getMyCompanyTasksInDynamicsAsync");
return new Promise(function(resolve, reject)

var MyCompanyTasksFetchXml = '<fetch version="1.0" output-format="xml-platform" mapping="logical" distinct="true" no-lock="false">';
MyCompanyTasksFetchXml = MyCompanyTasksFetchXml.concat(
'<entity name="MyCompany_MyCompanytask">',
'<attribute name="MyCompany_MyCompanytaskid" />',
'<attribute name="MyCompany_MyCompanytaskidreference" />',
'<attribute name="MyCompany_sdkmessagestepid" />',
'<filter type="and">',
'<condition attribute="MyCompany_MyCompanytaskid" operator="not-null" />',
'<condition attribute="MyCompany_MyCompanytaskidreference" operator="not-null" />',
'<condition attribute="MyCompany_sdkmessagestepid" operator="not-null" />',
'</filter>',
'<link-entity name="MyCompany_MyCompanytrigger" from="MyCompany_MyCompanytriggerid" to="MyCompany_MyCompanytriggerid" link-type="inner" alias="MyCompany_MyCompanytrigger" >',
'<attribute name="MyCompany_MyCompanytriggerid" />',
'<filter type="and">',
'<condition attribute="MyCompany_MyCompanytriggerid" operator="not-null" />',
'</filter>',
'</link-entity>',
'</entity>',
'</fetch>'
);
// TODO: Add filter to fetch xml to merge task instead of just drop and recreate table
// '<condition attribute="MyCompany_apiurl" operator="not-null" />',
// '<filter type="and">',
// '<condition attribute="MyCompany_MyCompanytaskidreference" operator="eq" value="' + MyCompanyTaskIdReference + '" />',
// '</filter>',
var MyCompanyTasksFetchXmlEncoded = encodeURI(MyCompanyTasksFetchXml);
var MyCompanyTasksRequestPath = "";
MyCompanyTasksRequestPath = MyCompanyTasksRequestPath.concat(
MyCompanyDynamicsClientApi.configuration.clientUrl,
"/api/data/v9.0/MyCompany_MyCompanytasks?fetchXml=",
MyCompanyTasksFetchXmlEncoded
);
MyCompanyDynamicsClientApi.request("GET", MyCompanyTasksRequestPath, "application/json").then(function (response)
try
MyCompanyDynamicsClientApi.configuration.MyCompanyChallengeTasksInDynamics = response;
resolve();
catch (error)
).catch(function(error) );
);

MyCompanyDynamicsClientApi.deleteMyCompanyTasksAsync = function()
console.log("Trying Promise for deleteMyCompanyTasksAsync");
return new Promise(function(resolve, reject)

var deleteSdkMessage = function (sdkmessagestepid)
return new Promise(function(resolve, reject)
if (sdkmessagestepid)
Xrm.WebApi.deleteRecord("sdkmessageprocessingstep", sdkmessagestepid).then(
function success(result)
console.log("Deleted sdkmessageprocessingstep " + result);
resolve();
,
function (error) console.log("ERROR: Xrm.WebApi.deleteRecord " + error.message.toString());
).catch(function(error)
reject(new Error("Failed Promise MyCompanyDynamicsClientApi.deleteMyCompanyTasksAsync " + error.message));
);
else
resolve();

);
;

var deleteMyCompanyTrigger = function (triggerid)
return new Promise(function(resolve, reject)
if (triggerid)
Xrm.WebApi.deleteRecord("MyCompany_MyCompanytrigger", triggerid).then(
function success(result)
console.log("Deleted MyCompany_MyCompanytrigger " + result);
resolve();
,
function (error) console.log("ERROR: Xrm.WebApi.deleteRecord " + error.message.toString());
).catch(function(error)
reject(new Error("Failed Promise MyCompanyDynamicsClientApi.deleteMyCompanyTasksAsync " + error.message));
);
else
resolve();

);
;

var deleteMyCompanyTask = function (taskid)
return new Promise(function(resolve, reject)
if (taskid)
Xrm.WebApi.deleteRecord("MyCompany_MyCompanytask", taskid).then(
function success(result)
console.log("Deleted MyCompany_MyCompanytask " + result);
resolve();
,
function (error) console.log("ERROR: Xrm.WebApi.deleteRecord " + error.message.toString());
).catch(function(error)
reject(new Error("Failed Promise MyCompanyDynamicsClientApi.deleteMyCompanyTasksAsync " + error.message));
);
else
resolve();

);
;

var deleteMyCompanyTasksAsyncPromises = ;
var MyCompanytaskid = "";
var MyCompanysdkmessagestepid = "";
var MyCompanytriggerid = "";
for (var i = 0; i < MyCompanyDynamicsClientApi.configuration.MyCompanyChallengeTasksInDynamics.value.length; i++)

if (MyCompanyDynamicsClientApi.configuration.MyCompanyChallengeTasksInDynamics.value[i])
MyCompanysdkmessagestepid = MyCompanyDynamicsClientApi.configuration.MyCompanyChallengeTasksInDynamics.value[i].MyCompany_sdkmessagestepid.toString();
deleteMyCompanyTasksAsyncPromises.push( deleteSdkMessage(MyCompanysdkmessagestepid) );

if (MyCompanyDynamicsClientApi.configuration.MyCompanyChallengeTasksInDynamics.value[i]["MyCompany_MyCompanytrigger.MyCompany_MyCompanytriggerid"])
MyCompanytriggerid = MyCompanyDynamicsClientApi.configuration.MyCompanyChallengeTasksInDynamics.value[i]["MyCompany_MyCompanytrigger.MyCompany_MyCompanytriggerid"].toString();
deleteMyCompanyTasksAsyncPromises.push( deleteMyCompanyTrigger(MyCompanytriggerid) );

if (MyCompanyDynamicsClientApi.configuration.MyCompanyChallengeTasksInDynamics.value[i].MyCompany_MyCompanytaskid)
MyCompanytaskid = MyCompanyDynamicsClientApi.configuration.MyCompanyChallengeTasksInDynamics.value[i].MyCompany_MyCompanytaskid.toString();
deleteMyCompanyTasksAsyncPromises.push( deleteMyCompanyTask(MyCompanytaskid) );


Promise.all(deleteMyCompanyTasksAsyncPromises).then(function()
resolve();
);
);

MyCompanyDynamicsClientApi.createMyCompanyTasksAsync = function()
console.log("Trying Promise for createMyCompanyTasksAsync");
return new Promise(function(resolve, reject)

var createMyCompanyTask = function (trigger)
return new Promise(function(resolve, reject)
if (trigger)
Xrm.WebApi.createRecord("MyCompany_MyCompanytrigger", trigger).then(
function success(result)
console.log("Created task and attached trigger to it, trigger is " + MyCompanyTrigger);
resolve();
,
function (error)
console.log("ERROR: Xrm.WebApi.createRecord " + error.message.toString());

).catch(function(error)
reject(new Error("Failed Promise MyCompanyDynamicsClientApi.createMyCompanyTasksAsync" + error.message));
);
else
resolve();

);
;

var createMyCompanyTasksAsyncPromises = ;
var MyCompanyTrigger = ;
var MyCompanyEntityName = "";
for (var i = 0; i < MyCompanyDynamicsClientApi.configuration.MyCompanyChallengeTasks.length; i++)

MyCompanyEntityName = MyCompanyDynamicsClientApi.configuration.MyCompanyChallengeTasks[i].primaryobjecttypecode[0].toUpperCase() + MyCompanyDynamicsClientApi.configuration.MyCompanyChallengeTasks[i].primaryobjecttypecode.slice(1);
MyCompanyTrigger =

"MyCompany_name": MyCompanyDynamicsClientApi.configuration.MyCompanyChallengeTasks[i].name + " " + MyCompanyEntityName,
"MyCompany_entityname": MyCompanyEntityName,
"MyCompany_messagename": MyCompanyDynamicsClientApi.configuration.MyCompanyChallengeTasks[i].name,
// MyCompany_MyCompany_MyCompanytask_MyCompany_MyCompanytrigger navigation property (1:N), from DataModel/MyCompanyCrmSdkTypes, generated class using the CrmSvcUtil.exe
"MyCompany_MyCompany_MyCompanytrigger_MyCompany_MyCompanytask_MyCompanytriggerid":
[

"MyCompany_name": MyCompanyDynamicsClientApi.configuration.MyCompanyChallengeTasks[i].Title,
"MyCompany_MyCompanytaskidreference": MyCompanyDynamicsClientApi.configuration.MyCompanyChallengeTasks[i].TaskId,
"MyCompany_enabled": true,
"MyCompany_pointvalue": MyCompanyDynamicsClientApi.configuration.MyCompanyChallengeTasks[i].EligiblePoints

]
;
createMyCompanyTasksAsyncPromises.push( createMyCompanyTask(MyCompanyTrigger) );


Promise.all(createMyCompanyTasksAsyncPromises).then(function()
resolve();
);
);


MyCompanyDynamicsClientApi.request = function(method, uri, contentType, otherHeaders, data)

if (!RegExp(method, "g").test("POST PATCH PUT GET DELETE"))
throw new Error("MyCompanyDynamicsClientApi.request: method must be: " +
"POST, PATCH, PUT, GET, or DELETE.");

if (!typeof uri === "string"

MyCompanyDynamicsClientApi.openSyncMyCompanyConfirmDialog = function()

console.log("Opening confirm dialog...");
var confirmStrings =
cancelButtonLabel: "No",
confirmButtonLabel: "Yes",
title: "MyCompany Sync",
subtitle: "Are you sure you want to start a sync?"
;
var confirmOptions = height: 200, width: 450 ;

Xrm.Navigation.openConfirmDialog(confirmStrings, confirmOptions).then(
success =>
if (success.confirmed)
console.log("openSyncMyCompanyConfirmDialog confirmed.");
MyCompanyDynamicsClientApi.runSyncMyCompanyProcess();

else
console.log("openSyncMyCompanyConfirmDialog closed.");

,
error =>
console.log(error.message);
);

MyCompanyDynamicsClientApi.openSyncMyCompanyCompleteDialog = function()
return new Promise(function(resolve, reject)
Xrm.Utility.closeProgressIndicator();
var alertStrings =
confirmButtonLabel: "Close",
text: "Sync Complete! You can safely close this window."
;
var alertOptions = height: 120, width: 260 ;
Xrm.Navigation.openAlertDialog(alertStrings, alertOptions).then(
function success(result)
resolve();
console.log("openSyncMyCompanyCompleteDialog dialog closed");
,
function (error) console.log(error.message);
);
);

MyCompanyDynamicsClientApi.openSyncMyCompanyFailedDialog = function(error)
return new Promise(function(resolve, reject)
Xrm.Utility.closeProgressIndicator();
var alertStrings =
confirmButtonLabel: "Close",
text: "ERROR :( the sync failed... please contact your admin. Error details belown" + error.toString()
;
var alertOptions = height: 120, width: 260 ;
Xrm.Navigation.openAlertDialog(alertStrings, alertOptions).then(
function success(result)
resolve();
console.log("openSyncMyCompanyFailedDialog dialog closed");
,
function (error) console.log(error.message);
);
);


function SyncMyCompany()

console.log("SyncMyCompany running...");
MyCompanyDynamicsClientApi.openSyncMyCompanyConfirmDialog();







share|improve this question













The main entry point is the SyncmyCompany() function.



I have a interesting scenario where I'm syncing data from a Company to a destination system. In this case Dynamics 365 is the destination system. The main flow of the code is:



  1. Get the configuration defined in Dynamics 365 and store data in my configuration object.

  2. Only once complete step 1, pass in company user creds to get a sts token I used in the company's api calls and store it in my configuration object.

  3. Only once complete step 2, get task from the company api.

  4. Only once complete step 3, get task that exists in Dynamics 365 if any.

  5. Only once complete step 4, if there are task in Dynamics 365 delete them using the task id and Xrm WebApi helper delete method. This requires some behind the scenes deletion of sdk message and task trigger dependencies too.

  6. Only once complete step 5, use the Xrm WebApi helper create method to repopulate the dropped task data with new data from the source company.

I am using Promises in order to make this possible.



What changes can I make to my Promise logic to make this cleaner and have a stronger order enforced in my code?



Code



"use strict";

var MyCompanyDynamicsClientApi = MyCompanyDynamicsClientApi || ;

MyCompanyDynamicsClientApi.configuration =
clientUrl: "",
MyCompanyApiUrl: "",
MyCompanyChallengeHostEmail: "",
MyCompanyChallengeHostPassword: "",
MyCompanyChallengeId: "",
MyCompanyChallengeTasks: ,
MyCompanyChallengeTasksInDynamics: ,
MyCompanyClientSecret: "",
MyCompanyUsername: "",
MyCompanyPassword: "",
stsToken: ""


MyCompanyDynamicsClientApi.runSyncMyCompanyProcess = function()


Xrm.Utility.showProgressIndicator("Syncing MyCompany Data...");
MyCompanyDynamicsClientApi.retrieveMyCompanyConfiguration().then(function ()
return MyCompanyDynamicsClientApi.getMyCompanyTokenAsync();
).then(function ()
return MyCompanyDynamicsClientApi.getMyCompanyTasksAsync();
).then(function ()
return MyCompanyDynamicsClientApi.getMyCompanyTasksInDynamicsAsync();
).then(function ()
return MyCompanyDynamicsClientApi.deleteMyCompanyTasksAsync();
).then(function ()
return MyCompanyDynamicsClientApi.createMyCompanyTasksAsync();
).then(function ()
return MyCompanyDynamicsClientApi.openSyncMyCompanyCompleteDialog();
).catch(function (error)
return MyCompanyDynamicsClientApi.openSyncMyCompanyFailedDialog(error());
);


MyCompanyDynamicsClientApi.retrieveMyCompanyConfiguration = function()
console.log("Trying Promise for MyCompanyConfiguration");
return new Promise(function(resolve, reject)
MyCompanyDynamicsClientApi.configuration.clientUrl = window.parent.Xrm.Utility.getGlobalContext().getClientUrl();

var MyCompanyConfigurationFetchXml = '<fetch version="1.0" output-format="xml-platform" mapping="logical" distinct="true" no-lock="false">';
MyCompanyConfigurationFetchXml = MyCompanyConfigurationFetchXml.concat(
'<entity name="MyCompany_MyCompanyconfiguration">',
'<attribute name="MyCompany_apiurl" />',
'<attribute name="MyCompany_challengehostemail" />',
'<attribute name="MyCompany_challengehostpassword" />',
'<attribute name="MyCompany_clientsecret" />',
'<attribute name="MyCompany_MyCompanyconfigurationid" />',
'<filter type="and">',
'<condition attribute="MyCompany_apiurl" operator="not-null" />',
'<condition attribute="MyCompany_challengehostemail" operator="not-null" />',
'<condition attribute="MyCompany_challengehostpassword" operator="not-null" />',
'<condition attribute="MyCompany_clientsecret" operator="not-null" />',
'<condition attribute="statecode" operator="eq" value="0" />',
'</filter>',
'<link-entity name="MyCompany_MyCompanychallenge" from="MyCompany_MyCompanyconfigurationid" to="MyCompany_MyCompanyconfigurationid" link-type="inner" alias="MyCompany_MyCompanychallenge">',
'<attribute name="MyCompany_MyCompanyid" />',
'<attribute name="MyCompany_name" />',
'<attribute name="MyCompany_MyCompanyconfigurationid" />',
'<filter type="and">',
'<condition attribute="MyCompany_MyCompanyid" operator="not-null" />',
'<condition attribute="MyCompany_name" operator="not-null" />',
'<condition attribute="MyCompany_MyCompanyconfigurationid" operator="not-null" />',
'<condition attribute="statecode" operator="eq" value="0" />',
'</filter>',
'</link-entity>',
'</entity>',
'</fetch>'
);
var MyCompanyConfigurationFetchXmlEncoded = encodeURI(MyCompanyConfigurationFetchXml);
var MyCompanyConfigurationRequestPath = "";
MyCompanyConfigurationRequestPath = MyCompanyConfigurationRequestPath.concat(
MyCompanyDynamicsClientApi.configuration.clientUrl,
"/api/data/v9.0/MyCompany_MyCompanyconfigurations?fetchXml=",
MyCompanyConfigurationFetchXmlEncoded
);

MyCompanyDynamicsClientApi.request("GET", MyCompanyConfigurationRequestPath, "application/json").then(function (response)
try
MyCompanyDynamicsClientApi.configuration.MyCompanyApiUrl = response.value[0].MyCompany_apiurl.toString();
MyCompanyDynamicsClientApi.configuration.MyCompanyChallengeHostEmail = response.value[0].MyCompany_challengehostemail.toString();
MyCompanyDynamicsClientApi.configuration.MyCompanyChallengeHostPassword = response.value[0].MyCompany_challengehostpassword.toString();
MyCompanyDynamicsClientApi.configuration.MyCompanyChallengeId = response.value[0]["MyCompany_MyCompanychallenge.MyCompany_MyCompanyid"].toString();
MyCompanyDynamicsClientApi.configuration.MyCompanyClientSecret = response.value[0].MyCompany_clientsecret.toString();
resolve();
catch (error) reject(new Error("Failed Promise MyCompanyDynamicsClientApi.retrieveMyCompanyConfiguration" + error.message));
).catch(function(error) reject(new Error("Failed Promise MyCompanyDynamicsClientApi.retrieveMyCompanyConfiguration" + error.message)); );
);

MyCompanyDynamicsClientApi.getMyCompanyTokenAsync = function()
console.log("Trying Promise for MyCompanyTokenAsync");
return new Promise(function(resolve, reject)

var stsToken = "";
var tokenRequestParams = "grant_type=password&scope=MyCompanyApi&username=" + MyCompanyDynamicsClientApi.configuration.MyCompanyChallengeHostEmail + "&password=" + MyCompanyDynamicsClientApi.configuration.MyCompanyChallengeHostPassword;
var tokenRequestHeaders = "Authorization": "Basic TWljcm9zb2Z0RmxvdzI6W0k0JjUoXVtPZUJsTk4laGduIXo5cH1TMmp5Wlt4Xmk5" ;
var stsTokenRequestUri = "https://playMyCompanydev.azurewebsites.net/sts/connect/token";
MyCompanyDynamicsClientApi.request("POST", stsTokenRequestUri, "application/x-www-form-urlencoded", tokenRequestHeaders, tokenRequestParams).then(function (response)
try
MyCompanyDynamicsClientApi.configuration.stsToken = response.access_token.toString();
resolve();
catch (error) reject(new Error("Failed Promise MyCompanyDynamicsClientApi.getMyCompanyTokenAsync" + error.message));
).catch(function(error) reject(new Error("Failed Promise MyCompanyDynamicsClientApi.getMyCompanyTokenAsync" + error.message)); );
);

MyCompanyDynamicsClientApi.getMyCompanyTasksAsync = function()
console.log("Trying Promise for getMyCompanyTasksAsync");
return new Promise(function(resolve, reject)

var taskRequestHeaders = "Authorization": "Bearer " + MyCompanyDynamicsClientApi.configuration.stsToken ;
var taskRequestUri = MyCompanyDynamicsClientApi.configuration.MyCompanyApiUrl + "dynamics/" + MyCompanyDynamicsClientApi.configuration.MyCompanyChallengeId + "/tasks";
MyCompanyDynamicsClientApi.request("GET", taskRequestUri, "application/json", taskRequestHeaders).then(function (response)
try
MyCompanyDynamicsClientApi.configuration.MyCompanyChallengeTasks = response;
resolve();
catch (error) reject(new Error("Failed Promise MyCompanyDynamicsClientApi.getMyCompanyTasksAsync" + error.message));
).catch(function(error) reject(new Error("Failed Promise MyCompanyDynamicsClientApi.getMyCompanyTasksAsync" + error.message)); );
);

MyCompanyDynamicsClientApi.getMyCompanyTasksInDynamicsAsync = function()
console.log("Trying Promise for getMyCompanyTasksInDynamicsAsync");
return new Promise(function(resolve, reject)

var MyCompanyTasksFetchXml = '<fetch version="1.0" output-format="xml-platform" mapping="logical" distinct="true" no-lock="false">';
MyCompanyTasksFetchXml = MyCompanyTasksFetchXml.concat(
'<entity name="MyCompany_MyCompanytask">',
'<attribute name="MyCompany_MyCompanytaskid" />',
'<attribute name="MyCompany_MyCompanytaskidreference" />',
'<attribute name="MyCompany_sdkmessagestepid" />',
'<filter type="and">',
'<condition attribute="MyCompany_MyCompanytaskid" operator="not-null" />',
'<condition attribute="MyCompany_MyCompanytaskidreference" operator="not-null" />',
'<condition attribute="MyCompany_sdkmessagestepid" operator="not-null" />',
'</filter>',
'<link-entity name="MyCompany_MyCompanytrigger" from="MyCompany_MyCompanytriggerid" to="MyCompany_MyCompanytriggerid" link-type="inner" alias="MyCompany_MyCompanytrigger" >',
'<attribute name="MyCompany_MyCompanytriggerid" />',
'<filter type="and">',
'<condition attribute="MyCompany_MyCompanytriggerid" operator="not-null" />',
'</filter>',
'</link-entity>',
'</entity>',
'</fetch>'
);
// TODO: Add filter to fetch xml to merge task instead of just drop and recreate table
// '<condition attribute="MyCompany_apiurl" operator="not-null" />',
// '<filter type="and">',
// '<condition attribute="MyCompany_MyCompanytaskidreference" operator="eq" value="' + MyCompanyTaskIdReference + '" />',
// '</filter>',
var MyCompanyTasksFetchXmlEncoded = encodeURI(MyCompanyTasksFetchXml);
var MyCompanyTasksRequestPath = "";
MyCompanyTasksRequestPath = MyCompanyTasksRequestPath.concat(
MyCompanyDynamicsClientApi.configuration.clientUrl,
"/api/data/v9.0/MyCompany_MyCompanytasks?fetchXml=",
MyCompanyTasksFetchXmlEncoded
);
MyCompanyDynamicsClientApi.request("GET", MyCompanyTasksRequestPath, "application/json").then(function (response)
try
MyCompanyDynamicsClientApi.configuration.MyCompanyChallengeTasksInDynamics = response;
resolve();
catch (error)
).catch(function(error) );
);

MyCompanyDynamicsClientApi.deleteMyCompanyTasksAsync = function()
console.log("Trying Promise for deleteMyCompanyTasksAsync");
return new Promise(function(resolve, reject)

var deleteSdkMessage = function (sdkmessagestepid)
return new Promise(function(resolve, reject)
if (sdkmessagestepid)
Xrm.WebApi.deleteRecord("sdkmessageprocessingstep", sdkmessagestepid).then(
function success(result)
console.log("Deleted sdkmessageprocessingstep " + result);
resolve();
,
function (error) console.log("ERROR: Xrm.WebApi.deleteRecord " + error.message.toString());
).catch(function(error)
reject(new Error("Failed Promise MyCompanyDynamicsClientApi.deleteMyCompanyTasksAsync " + error.message));
);
else
resolve();

);
;

var deleteMyCompanyTrigger = function (triggerid)
return new Promise(function(resolve, reject)
if (triggerid)
Xrm.WebApi.deleteRecord("MyCompany_MyCompanytrigger", triggerid).then(
function success(result)
console.log("Deleted MyCompany_MyCompanytrigger " + result);
resolve();
,
function (error) console.log("ERROR: Xrm.WebApi.deleteRecord " + error.message.toString());
).catch(function(error)
reject(new Error("Failed Promise MyCompanyDynamicsClientApi.deleteMyCompanyTasksAsync " + error.message));
);
else
resolve();

);
;

var deleteMyCompanyTask = function (taskid)
return new Promise(function(resolve, reject)
if (taskid)
Xrm.WebApi.deleteRecord("MyCompany_MyCompanytask", taskid).then(
function success(result)
console.log("Deleted MyCompany_MyCompanytask " + result);
resolve();
,
function (error) console.log("ERROR: Xrm.WebApi.deleteRecord " + error.message.toString());
).catch(function(error)
reject(new Error("Failed Promise MyCompanyDynamicsClientApi.deleteMyCompanyTasksAsync " + error.message));
);
else
resolve();

);
;

var deleteMyCompanyTasksAsyncPromises = ;
var MyCompanytaskid = "";
var MyCompanysdkmessagestepid = "";
var MyCompanytriggerid = "";
for (var i = 0; i < MyCompanyDynamicsClientApi.configuration.MyCompanyChallengeTasksInDynamics.value.length; i++)

if (MyCompanyDynamicsClientApi.configuration.MyCompanyChallengeTasksInDynamics.value[i])
MyCompanysdkmessagestepid = MyCompanyDynamicsClientApi.configuration.MyCompanyChallengeTasksInDynamics.value[i].MyCompany_sdkmessagestepid.toString();
deleteMyCompanyTasksAsyncPromises.push( deleteSdkMessage(MyCompanysdkmessagestepid) );

if (MyCompanyDynamicsClientApi.configuration.MyCompanyChallengeTasksInDynamics.value[i]["MyCompany_MyCompanytrigger.MyCompany_MyCompanytriggerid"])
MyCompanytriggerid = MyCompanyDynamicsClientApi.configuration.MyCompanyChallengeTasksInDynamics.value[i]["MyCompany_MyCompanytrigger.MyCompany_MyCompanytriggerid"].toString();
deleteMyCompanyTasksAsyncPromises.push( deleteMyCompanyTrigger(MyCompanytriggerid) );

if (MyCompanyDynamicsClientApi.configuration.MyCompanyChallengeTasksInDynamics.value[i].MyCompany_MyCompanytaskid)
MyCompanytaskid = MyCompanyDynamicsClientApi.configuration.MyCompanyChallengeTasksInDynamics.value[i].MyCompany_MyCompanytaskid.toString();
deleteMyCompanyTasksAsyncPromises.push( deleteMyCompanyTask(MyCompanytaskid) );


Promise.all(deleteMyCompanyTasksAsyncPromises).then(function()
resolve();
);
);

MyCompanyDynamicsClientApi.createMyCompanyTasksAsync = function()
console.log("Trying Promise for createMyCompanyTasksAsync");
return new Promise(function(resolve, reject)

var createMyCompanyTask = function (trigger)
return new Promise(function(resolve, reject)
if (trigger)
Xrm.WebApi.createRecord("MyCompany_MyCompanytrigger", trigger).then(
function success(result)
console.log("Created task and attached trigger to it, trigger is " + MyCompanyTrigger);
resolve();
,
function (error)
console.log("ERROR: Xrm.WebApi.createRecord " + error.message.toString());

).catch(function(error)
reject(new Error("Failed Promise MyCompanyDynamicsClientApi.createMyCompanyTasksAsync" + error.message));
);
else
resolve();

);
;

var createMyCompanyTasksAsyncPromises = ;
var MyCompanyTrigger = ;
var MyCompanyEntityName = "";
for (var i = 0; i < MyCompanyDynamicsClientApi.configuration.MyCompanyChallengeTasks.length; i++)

MyCompanyEntityName = MyCompanyDynamicsClientApi.configuration.MyCompanyChallengeTasks[i].primaryobjecttypecode[0].toUpperCase() + MyCompanyDynamicsClientApi.configuration.MyCompanyChallengeTasks[i].primaryobjecttypecode.slice(1);
MyCompanyTrigger =

"MyCompany_name": MyCompanyDynamicsClientApi.configuration.MyCompanyChallengeTasks[i].name + " " + MyCompanyEntityName,
"MyCompany_entityname": MyCompanyEntityName,
"MyCompany_messagename": MyCompanyDynamicsClientApi.configuration.MyCompanyChallengeTasks[i].name,
// MyCompany_MyCompany_MyCompanytask_MyCompany_MyCompanytrigger navigation property (1:N), from DataModel/MyCompanyCrmSdkTypes, generated class using the CrmSvcUtil.exe
"MyCompany_MyCompany_MyCompanytrigger_MyCompany_MyCompanytask_MyCompanytriggerid":
[

"MyCompany_name": MyCompanyDynamicsClientApi.configuration.MyCompanyChallengeTasks[i].Title,
"MyCompany_MyCompanytaskidreference": MyCompanyDynamicsClientApi.configuration.MyCompanyChallengeTasks[i].TaskId,
"MyCompany_enabled": true,
"MyCompany_pointvalue": MyCompanyDynamicsClientApi.configuration.MyCompanyChallengeTasks[i].EligiblePoints

]
;
createMyCompanyTasksAsyncPromises.push( createMyCompanyTask(MyCompanyTrigger) );


Promise.all(createMyCompanyTasksAsyncPromises).then(function()
resolve();
);
);


MyCompanyDynamicsClientApi.request = function(method, uri, contentType, otherHeaders, data)

if (!RegExp(method, "g").test("POST PATCH PUT GET DELETE"))
throw new Error("MyCompanyDynamicsClientApi.request: method must be: " +
"POST, PATCH, PUT, GET, or DELETE.");

if (!typeof uri === "string"

MyCompanyDynamicsClientApi.openSyncMyCompanyConfirmDialog = function()

console.log("Opening confirm dialog...");
var confirmStrings =
cancelButtonLabel: "No",
confirmButtonLabel: "Yes",
title: "MyCompany Sync",
subtitle: "Are you sure you want to start a sync?"
;
var confirmOptions = height: 200, width: 450 ;

Xrm.Navigation.openConfirmDialog(confirmStrings, confirmOptions).then(
success =>
if (success.confirmed)
console.log("openSyncMyCompanyConfirmDialog confirmed.");
MyCompanyDynamicsClientApi.runSyncMyCompanyProcess();

else
console.log("openSyncMyCompanyConfirmDialog closed.");

,
error =>
console.log(error.message);
);

MyCompanyDynamicsClientApi.openSyncMyCompanyCompleteDialog = function()
return new Promise(function(resolve, reject)
Xrm.Utility.closeProgressIndicator();
var alertStrings =
confirmButtonLabel: "Close",
text: "Sync Complete! You can safely close this window."
;
var alertOptions = height: 120, width: 260 ;
Xrm.Navigation.openAlertDialog(alertStrings, alertOptions).then(
function success(result)
resolve();
console.log("openSyncMyCompanyCompleteDialog dialog closed");
,
function (error) console.log(error.message);
);
);

MyCompanyDynamicsClientApi.openSyncMyCompanyFailedDialog = function(error)
return new Promise(function(resolve, reject)
Xrm.Utility.closeProgressIndicator();
var alertStrings =
confirmButtonLabel: "Close",
text: "ERROR :( the sync failed... please contact your admin. Error details belown" + error.toString()
;
var alertOptions = height: 120, width: 260 ;
Xrm.Navigation.openAlertDialog(alertStrings, alertOptions).then(
function success(result)
resolve();
console.log("openSyncMyCompanyFailedDialog dialog closed");
,
function (error) console.log(error.message);
);
);


function SyncMyCompany()

console.log("SyncMyCompany running...");
MyCompanyDynamicsClientApi.openSyncMyCompanyConfirmDialog();









share|improve this question












share|improve this question




share|improve this question








edited Jun 9 at 2:26
























asked Jun 8 at 17:47









Greg Degruy

1676




1676







  • 1




    updated question to call this out and highlight review of the syntax and structure of code.
    – Greg Degruy
    Jun 8 at 18:47










  • This is better IMO. Thanks
    – Igor Soloydenko
    Jun 8 at 19:08










  • completed edits
    – Greg Degruy
    Jun 8 at 21:58












  • 1




    updated question to call this out and highlight review of the syntax and structure of code.
    – Greg Degruy
    Jun 8 at 18:47










  • This is better IMO. Thanks
    – Igor Soloydenko
    Jun 8 at 19:08










  • completed edits
    – Greg Degruy
    Jun 8 at 21:58







1




1




updated question to call this out and highlight review of the syntax and structure of code.
– Greg Degruy
Jun 8 at 18:47




updated question to call this out and highlight review of the syntax and structure of code.
– Greg Degruy
Jun 8 at 18:47












This is better IMO. Thanks
– Igor Soloydenko
Jun 8 at 19:08




This is better IMO. Thanks
– Igor Soloydenko
Jun 8 at 19:08












completed edits
– Greg Degruy
Jun 8 at 21:58




completed edits
– Greg Degruy
Jun 8 at 21:58










1 Answer
1






active

oldest

votes

















up vote
2
down vote



accepted










For how that code looks like, and based on all the methods being just namespaced within MyCompanyDynamicsClientApi, you could remove all the function() wraps between the calls, giving you something like:



MyCompanyDynamicsClientApi.runSyncMyCompanyProcess = function() 
Xrm.Utility.showProgressIndicator("Syncing MyCompany Data...");
MyCompanyDynamicsClientApi.retrieveMyCompanyConfiguration()
.then(MyCompanyDynamicsClientApi.getMyCompanyTokenAsync)
.then(MyCompanyDynamicsClientApi.getMyCompanyTasksAsync)
.then(MyCompanyDynamicsClientApi.getMyCompanyTasksInDynamicsAsync)
.then(MyCompanyDynamicsClientApi.deleteMyCompanyTasksAsync)
.then(MyCompanyDynamicsClientApi.createMyCompanyTasksAsync)
.then(MyCompanyDynamicsClientApi.openSyncMyCompanyCompleteDialog)
.catch(function (error)
return MyCompanyDynamicsClientApi.openSyncMyCompanyFailedDialog(error());
);



on top of that, I don't know why you are calling the error. It doesn't seem to be a function.



MyCompanyDynamicsClientApi.runSyncMyCompanyProcess = function() 
Xrm.Utility.showProgressIndicator("Syncing MyCompany Data...");
MyCompanyDynamicsClientApi.retrieveMyCompanyConfiguration()
.then(MyCompanyDynamicsClientApi.getMyCompanyTokenAsync)
.then(MyCompanyDynamicsClientApi.getMyCompanyTasksAsync)
.then(MyCompanyDynamicsClientApi.getMyCompanyTasksInDynamicsAsync)
.then(MyCompanyDynamicsClientApi.deleteMyCompanyTasksAsync)
.then(MyCompanyDynamicsClientApi.createMyCompanyTasksAsync)
.then(MyCompanyDynamicsClientApi.openSyncMyCompanyCompleteDialog)
.catch(MyCompanyDynamicsClientApi.openSyncMyCompanyFailedDialog);



For most of your code you can just return the promise returned by .request, and throw in the other errors, because you are already in a Promise chain, if you throw an error it will reject the promise chain and trigger the catch.



MyCompanyDynamicsClientApi.getMyCompanyTokenAsync = function() 
console.log("Trying Promise for MyCompanyTokenAsync");
// Anything failing here will make the outer promise to fail and catch to be called
var stsToken = "";
var tokenRequestParams = "grant_type=password&scope=MyCompanyApi&username=" + MyCompanyDynamicsClientApi.configuration.MyCompanyChallengeHostEmail + "&password=" + MyCompanyDynamicsClientApi.configuration.MyCompanyChallengeHostPassword;
var tokenRequestHeaders = "Authorization": "Basic TWljcm9zb2Z0RmxvdzI6W0k0JjUoXVtPZUJsTk4laGduIXo5cH1TMmp5Wlt4Xmk5" ;
var stsTokenRequestUri = "https://playMyCompanydev.azurewebsites.net/sts/connect/token";

return MyCompanyDynamicsClientApi.request("POST", stsTokenRequestUri, "application/x-www-form-urlencoded", tokenRequestHeaders, tokenRequestParams)
.then(function (response)
// Anything failing here will make this promise to fail and catch to be called
MyCompanyDynamicsClientApi.configuration.stsToken = response.access_token.toString();
)
.catch(function(error) throw new Error("Failed Promise MyCompanyDynamicsClientApi.getMyCompanyTokenAsync" + error.message); );
);
}


Other stuff not necessarily related to Promises:



If this runs in node, consider using template literals: ``



var MyCompanyConfigurationFetchXml = `
<fetch version="1.0" output-format="xml-platform" mapping="logical" distinct="true" no-lock="false">
<entity name="MyCompany_MyCompanyconfiguration">
<attribute name="MyCompany_apiurl" />
<attribute name="MyCompany_challengehostemail" />
<attribute name="MyCompany_challengehostpassword" />
<attribute name="MyCompany_clientsecret" />
<attribute name="MyCompany_MyCompanyconfigurationid" />
<filter type="and">
<condition attribute="MyCompany_apiurl" operator="not-null" />
<condition attribute="MyCompany_challengehostemail" operator="not-null" />
<condition attribute="MyCompany_challengehostpassword" operator="not-null" />
<condition attribute="MyCompany_clientsecret" operator="not-null" />
<condition attribute="statecode" operator="eq" value="0" />
</filter>
<link-entity name="MyCompany_MyCompanychallenge" from="MyCompany_MyCompanyconfigurationid" to="MyCompany_MyCompanyconfigurationid" link-type="inner" alias="MyCompany_MyCompanychallenge">
<attribute name="MyCompany_MyCompanyid" />
<attribute name="MyCompany_name" />
<attribute name="MyCompany_MyCompanyconfigurationid" />
<filter type="and">
<condition attribute="MyCompany_MyCompanyid" operator="not-null" />
<condition attribute="MyCompany_name" operator="not-null" />
<condition attribute="MyCompany_MyCompanyconfigurationid" operator="not-null" />
<condition attribute="statecode" operator="eq" value="0" />
</filter>
</link-entity>
</entity>
</fetch>`;


Extracting Constants and Mappers to separate methods:



function mapValues(response) 
var values = response.values[0];

return
MyCompanyApiUrl: values.MyCompany_apiurl.toString(),
MyCompanyChallengeHostEmail: values.MyCompany_challengehostemail.toString(),
MyCompanyChallengeHostPassword: values.MyCompany_challengehostpassword.toString(),
MyCompanyChallengeId: values["MyCompany_MyCompanychallenge.MyCompany_MyCompanyid"].toString(),
MyCompanyClientSecret: values.MyCompany_clientsecret.toString(),
;


MyCompanyDynamicsClientApi.retrieveMyCompanyConfiguration = function()
console.log("Trying Promise for MyCompanyConfiguration");
MyCompanyDynamicsClientApi.configuration.clientUrl = window.parent.Xrm.Utility.getGlobalContext().getClientUrl();

var requestPath = getCompanyConfigurationRequestPath(MyCompanyDynamicsClientApi.configuration.clientUrl);

MyCompanyDynamicsClientApi.request("GET", requestPath, "application/json")
.then(function(response)
Object.assign(MyCompanyDynamicsClientApi.configuration, mapValues(response));
)
.catch(function(error)
throw new Error("Failed Promise MyCompanyDynamicsClientApi.retrieveMyCompanyConfiguration" + error.message);
);
);
}


You might also want to split this in separate files or/and encapsulate things better so you can rely less on namespaces. It would probably make the code more readable.



Regarding the order enforcement, promises should handle that properly. If you want to parallelise some work, you can also use Promise.all().



https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Promise/all






share|improve this answer





















  • perfect, these are the suggestions I was looking for. I am considering using node for dev, using require/imports, then minimiaing all of my code into one Main.js file. Is this possible? In production the platform will only accept pure js in a single file.
    – Greg Degruy
    Jun 21 at 21:46










  • also, what is your opinion on name spacing in the way I have done here? I want to make sure my library is unique for the partners I work with.
    – Greg Degruy
    Jun 22 at 0:46






  • 1




    The namespacing you did is ok, but you should consider to only expose the public interface of your module. If all users can call is one method, make that public and keep everything else out of the namespace. You could consider using a bundler like webpack or rollup, it will help with the encapsulation and testability.
    – Ignacio Catalina
    Jun 22 at 1:59










  • awesome, this was really helpful. going to start refactoring my projects right now!
    – Greg Degruy
    Jun 22 at 2:02










Your Answer




StackExchange.ifUsing("editor", function ()
return StackExchange.using("mathjaxEditing", function ()
StackExchange.MarkdownEditor.creationCallbacks.add(function (editor, postfix)
StackExchange.mathjaxEditing.prepareWmdForMathJax(editor, postfix, [["\$", "\$"]]);
);
);
, "mathjax-editing");

StackExchange.ifUsing("editor", function ()
StackExchange.using("externalEditor", function ()
StackExchange.using("snippets", function ()
StackExchange.snippets.init();
);
);
, "code-snippets");

StackExchange.ready(function()
var channelOptions =
tags: "".split(" "),
id: "196"
;
initTagRenderer("".split(" "), "".split(" "), channelOptions);

StackExchange.using("externalEditor", function()
// Have to fire editor after snippets, if snippets enabled
if (StackExchange.settings.snippets.snippetsEnabled)
StackExchange.using("snippets", function()
createEditor();
);

else
createEditor();

);

function createEditor()
StackExchange.prepareEditor(
heartbeatType: 'answer',
convertImagesToLinks: false,
noModals: false,
showLowRepImageUploadWarning: true,
reputationToPostImages: null,
bindNavPrevention: true,
postfix: "",
onDemand: true,
discardSelector: ".discard-answer"
,immediatelyShowMarkdownHelp:true
);



);








 

draft saved


draft discarded


















StackExchange.ready(
function ()
StackExchange.openid.initPostLogin('.new-post-login', 'https%3a%2f%2fcodereview.stackexchange.com%2fquestions%2f196127%2fenforce-request-completion-order-using-multiple-promise-chaining%23new-answer', 'question_page');

);

Post as a guest






























1 Answer
1






active

oldest

votes








1 Answer
1






active

oldest

votes









active

oldest

votes






active

oldest

votes








up vote
2
down vote



accepted










For how that code looks like, and based on all the methods being just namespaced within MyCompanyDynamicsClientApi, you could remove all the function() wraps between the calls, giving you something like:



MyCompanyDynamicsClientApi.runSyncMyCompanyProcess = function() 
Xrm.Utility.showProgressIndicator("Syncing MyCompany Data...");
MyCompanyDynamicsClientApi.retrieveMyCompanyConfiguration()
.then(MyCompanyDynamicsClientApi.getMyCompanyTokenAsync)
.then(MyCompanyDynamicsClientApi.getMyCompanyTasksAsync)
.then(MyCompanyDynamicsClientApi.getMyCompanyTasksInDynamicsAsync)
.then(MyCompanyDynamicsClientApi.deleteMyCompanyTasksAsync)
.then(MyCompanyDynamicsClientApi.createMyCompanyTasksAsync)
.then(MyCompanyDynamicsClientApi.openSyncMyCompanyCompleteDialog)
.catch(function (error)
return MyCompanyDynamicsClientApi.openSyncMyCompanyFailedDialog(error());
);



on top of that, I don't know why you are calling the error. It doesn't seem to be a function.



MyCompanyDynamicsClientApi.runSyncMyCompanyProcess = function() 
Xrm.Utility.showProgressIndicator("Syncing MyCompany Data...");
MyCompanyDynamicsClientApi.retrieveMyCompanyConfiguration()
.then(MyCompanyDynamicsClientApi.getMyCompanyTokenAsync)
.then(MyCompanyDynamicsClientApi.getMyCompanyTasksAsync)
.then(MyCompanyDynamicsClientApi.getMyCompanyTasksInDynamicsAsync)
.then(MyCompanyDynamicsClientApi.deleteMyCompanyTasksAsync)
.then(MyCompanyDynamicsClientApi.createMyCompanyTasksAsync)
.then(MyCompanyDynamicsClientApi.openSyncMyCompanyCompleteDialog)
.catch(MyCompanyDynamicsClientApi.openSyncMyCompanyFailedDialog);



For most of your code you can just return the promise returned by .request, and throw in the other errors, because you are already in a Promise chain, if you throw an error it will reject the promise chain and trigger the catch.



MyCompanyDynamicsClientApi.getMyCompanyTokenAsync = function() 
console.log("Trying Promise for MyCompanyTokenAsync");
// Anything failing here will make the outer promise to fail and catch to be called
var stsToken = "";
var tokenRequestParams = "grant_type=password&scope=MyCompanyApi&username=" + MyCompanyDynamicsClientApi.configuration.MyCompanyChallengeHostEmail + "&password=" + MyCompanyDynamicsClientApi.configuration.MyCompanyChallengeHostPassword;
var tokenRequestHeaders = "Authorization": "Basic TWljcm9zb2Z0RmxvdzI6W0k0JjUoXVtPZUJsTk4laGduIXo5cH1TMmp5Wlt4Xmk5" ;
var stsTokenRequestUri = "https://playMyCompanydev.azurewebsites.net/sts/connect/token";

return MyCompanyDynamicsClientApi.request("POST", stsTokenRequestUri, "application/x-www-form-urlencoded", tokenRequestHeaders, tokenRequestParams)
.then(function (response)
// Anything failing here will make this promise to fail and catch to be called
MyCompanyDynamicsClientApi.configuration.stsToken = response.access_token.toString();
)
.catch(function(error) throw new Error("Failed Promise MyCompanyDynamicsClientApi.getMyCompanyTokenAsync" + error.message); );
);
}


Other stuff not necessarily related to Promises:



If this runs in node, consider using template literals: ``



var MyCompanyConfigurationFetchXml = `
<fetch version="1.0" output-format="xml-platform" mapping="logical" distinct="true" no-lock="false">
<entity name="MyCompany_MyCompanyconfiguration">
<attribute name="MyCompany_apiurl" />
<attribute name="MyCompany_challengehostemail" />
<attribute name="MyCompany_challengehostpassword" />
<attribute name="MyCompany_clientsecret" />
<attribute name="MyCompany_MyCompanyconfigurationid" />
<filter type="and">
<condition attribute="MyCompany_apiurl" operator="not-null" />
<condition attribute="MyCompany_challengehostemail" operator="not-null" />
<condition attribute="MyCompany_challengehostpassword" operator="not-null" />
<condition attribute="MyCompany_clientsecret" operator="not-null" />
<condition attribute="statecode" operator="eq" value="0" />
</filter>
<link-entity name="MyCompany_MyCompanychallenge" from="MyCompany_MyCompanyconfigurationid" to="MyCompany_MyCompanyconfigurationid" link-type="inner" alias="MyCompany_MyCompanychallenge">
<attribute name="MyCompany_MyCompanyid" />
<attribute name="MyCompany_name" />
<attribute name="MyCompany_MyCompanyconfigurationid" />
<filter type="and">
<condition attribute="MyCompany_MyCompanyid" operator="not-null" />
<condition attribute="MyCompany_name" operator="not-null" />
<condition attribute="MyCompany_MyCompanyconfigurationid" operator="not-null" />
<condition attribute="statecode" operator="eq" value="0" />
</filter>
</link-entity>
</entity>
</fetch>`;


Extracting Constants and Mappers to separate methods:



function mapValues(response) 
var values = response.values[0];

return
MyCompanyApiUrl: values.MyCompany_apiurl.toString(),
MyCompanyChallengeHostEmail: values.MyCompany_challengehostemail.toString(),
MyCompanyChallengeHostPassword: values.MyCompany_challengehostpassword.toString(),
MyCompanyChallengeId: values["MyCompany_MyCompanychallenge.MyCompany_MyCompanyid"].toString(),
MyCompanyClientSecret: values.MyCompany_clientsecret.toString(),
;


MyCompanyDynamicsClientApi.retrieveMyCompanyConfiguration = function()
console.log("Trying Promise for MyCompanyConfiguration");
MyCompanyDynamicsClientApi.configuration.clientUrl = window.parent.Xrm.Utility.getGlobalContext().getClientUrl();

var requestPath = getCompanyConfigurationRequestPath(MyCompanyDynamicsClientApi.configuration.clientUrl);

MyCompanyDynamicsClientApi.request("GET", requestPath, "application/json")
.then(function(response)
Object.assign(MyCompanyDynamicsClientApi.configuration, mapValues(response));
)
.catch(function(error)
throw new Error("Failed Promise MyCompanyDynamicsClientApi.retrieveMyCompanyConfiguration" + error.message);
);
);
}


You might also want to split this in separate files or/and encapsulate things better so you can rely less on namespaces. It would probably make the code more readable.



Regarding the order enforcement, promises should handle that properly. If you want to parallelise some work, you can also use Promise.all().



https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Promise/all






share|improve this answer





















  • perfect, these are the suggestions I was looking for. I am considering using node for dev, using require/imports, then minimiaing all of my code into one Main.js file. Is this possible? In production the platform will only accept pure js in a single file.
    – Greg Degruy
    Jun 21 at 21:46










  • also, what is your opinion on name spacing in the way I have done here? I want to make sure my library is unique for the partners I work with.
    – Greg Degruy
    Jun 22 at 0:46






  • 1




    The namespacing you did is ok, but you should consider to only expose the public interface of your module. If all users can call is one method, make that public and keep everything else out of the namespace. You could consider using a bundler like webpack or rollup, it will help with the encapsulation and testability.
    – Ignacio Catalina
    Jun 22 at 1:59










  • awesome, this was really helpful. going to start refactoring my projects right now!
    – Greg Degruy
    Jun 22 at 2:02














up vote
2
down vote



accepted










For how that code looks like, and based on all the methods being just namespaced within MyCompanyDynamicsClientApi, you could remove all the function() wraps between the calls, giving you something like:



MyCompanyDynamicsClientApi.runSyncMyCompanyProcess = function() 
Xrm.Utility.showProgressIndicator("Syncing MyCompany Data...");
MyCompanyDynamicsClientApi.retrieveMyCompanyConfiguration()
.then(MyCompanyDynamicsClientApi.getMyCompanyTokenAsync)
.then(MyCompanyDynamicsClientApi.getMyCompanyTasksAsync)
.then(MyCompanyDynamicsClientApi.getMyCompanyTasksInDynamicsAsync)
.then(MyCompanyDynamicsClientApi.deleteMyCompanyTasksAsync)
.then(MyCompanyDynamicsClientApi.createMyCompanyTasksAsync)
.then(MyCompanyDynamicsClientApi.openSyncMyCompanyCompleteDialog)
.catch(function (error)
return MyCompanyDynamicsClientApi.openSyncMyCompanyFailedDialog(error());
);



on top of that, I don't know why you are calling the error. It doesn't seem to be a function.



MyCompanyDynamicsClientApi.runSyncMyCompanyProcess = function() 
Xrm.Utility.showProgressIndicator("Syncing MyCompany Data...");
MyCompanyDynamicsClientApi.retrieveMyCompanyConfiguration()
.then(MyCompanyDynamicsClientApi.getMyCompanyTokenAsync)
.then(MyCompanyDynamicsClientApi.getMyCompanyTasksAsync)
.then(MyCompanyDynamicsClientApi.getMyCompanyTasksInDynamicsAsync)
.then(MyCompanyDynamicsClientApi.deleteMyCompanyTasksAsync)
.then(MyCompanyDynamicsClientApi.createMyCompanyTasksAsync)
.then(MyCompanyDynamicsClientApi.openSyncMyCompanyCompleteDialog)
.catch(MyCompanyDynamicsClientApi.openSyncMyCompanyFailedDialog);



For most of your code you can just return the promise returned by .request, and throw in the other errors, because you are already in a Promise chain, if you throw an error it will reject the promise chain and trigger the catch.



MyCompanyDynamicsClientApi.getMyCompanyTokenAsync = function() 
console.log("Trying Promise for MyCompanyTokenAsync");
// Anything failing here will make the outer promise to fail and catch to be called
var stsToken = "";
var tokenRequestParams = "grant_type=password&scope=MyCompanyApi&username=" + MyCompanyDynamicsClientApi.configuration.MyCompanyChallengeHostEmail + "&password=" + MyCompanyDynamicsClientApi.configuration.MyCompanyChallengeHostPassword;
var tokenRequestHeaders = "Authorization": "Basic TWljcm9zb2Z0RmxvdzI6W0k0JjUoXVtPZUJsTk4laGduIXo5cH1TMmp5Wlt4Xmk5" ;
var stsTokenRequestUri = "https://playMyCompanydev.azurewebsites.net/sts/connect/token";

return MyCompanyDynamicsClientApi.request("POST", stsTokenRequestUri, "application/x-www-form-urlencoded", tokenRequestHeaders, tokenRequestParams)
.then(function (response)
// Anything failing here will make this promise to fail and catch to be called
MyCompanyDynamicsClientApi.configuration.stsToken = response.access_token.toString();
)
.catch(function(error) throw new Error("Failed Promise MyCompanyDynamicsClientApi.getMyCompanyTokenAsync" + error.message); );
);
}


Other stuff not necessarily related to Promises:



If this runs in node, consider using template literals: ``



var MyCompanyConfigurationFetchXml = `
<fetch version="1.0" output-format="xml-platform" mapping="logical" distinct="true" no-lock="false">
<entity name="MyCompany_MyCompanyconfiguration">
<attribute name="MyCompany_apiurl" />
<attribute name="MyCompany_challengehostemail" />
<attribute name="MyCompany_challengehostpassword" />
<attribute name="MyCompany_clientsecret" />
<attribute name="MyCompany_MyCompanyconfigurationid" />
<filter type="and">
<condition attribute="MyCompany_apiurl" operator="not-null" />
<condition attribute="MyCompany_challengehostemail" operator="not-null" />
<condition attribute="MyCompany_challengehostpassword" operator="not-null" />
<condition attribute="MyCompany_clientsecret" operator="not-null" />
<condition attribute="statecode" operator="eq" value="0" />
</filter>
<link-entity name="MyCompany_MyCompanychallenge" from="MyCompany_MyCompanyconfigurationid" to="MyCompany_MyCompanyconfigurationid" link-type="inner" alias="MyCompany_MyCompanychallenge">
<attribute name="MyCompany_MyCompanyid" />
<attribute name="MyCompany_name" />
<attribute name="MyCompany_MyCompanyconfigurationid" />
<filter type="and">
<condition attribute="MyCompany_MyCompanyid" operator="not-null" />
<condition attribute="MyCompany_name" operator="not-null" />
<condition attribute="MyCompany_MyCompanyconfigurationid" operator="not-null" />
<condition attribute="statecode" operator="eq" value="0" />
</filter>
</link-entity>
</entity>
</fetch>`;


Extracting Constants and Mappers to separate methods:



function mapValues(response) 
var values = response.values[0];

return
MyCompanyApiUrl: values.MyCompany_apiurl.toString(),
MyCompanyChallengeHostEmail: values.MyCompany_challengehostemail.toString(),
MyCompanyChallengeHostPassword: values.MyCompany_challengehostpassword.toString(),
MyCompanyChallengeId: values["MyCompany_MyCompanychallenge.MyCompany_MyCompanyid"].toString(),
MyCompanyClientSecret: values.MyCompany_clientsecret.toString(),
;


MyCompanyDynamicsClientApi.retrieveMyCompanyConfiguration = function()
console.log("Trying Promise for MyCompanyConfiguration");
MyCompanyDynamicsClientApi.configuration.clientUrl = window.parent.Xrm.Utility.getGlobalContext().getClientUrl();

var requestPath = getCompanyConfigurationRequestPath(MyCompanyDynamicsClientApi.configuration.clientUrl);

MyCompanyDynamicsClientApi.request("GET", requestPath, "application/json")
.then(function(response)
Object.assign(MyCompanyDynamicsClientApi.configuration, mapValues(response));
)
.catch(function(error)
throw new Error("Failed Promise MyCompanyDynamicsClientApi.retrieveMyCompanyConfiguration" + error.message);
);
);
}


You might also want to split this in separate files or/and encapsulate things better so you can rely less on namespaces. It would probably make the code more readable.



Regarding the order enforcement, promises should handle that properly. If you want to parallelise some work, you can also use Promise.all().



https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Promise/all






share|improve this answer





















  • perfect, these are the suggestions I was looking for. I am considering using node for dev, using require/imports, then minimiaing all of my code into one Main.js file. Is this possible? In production the platform will only accept pure js in a single file.
    – Greg Degruy
    Jun 21 at 21:46










  • also, what is your opinion on name spacing in the way I have done here? I want to make sure my library is unique for the partners I work with.
    – Greg Degruy
    Jun 22 at 0:46






  • 1




    The namespacing you did is ok, but you should consider to only expose the public interface of your module. If all users can call is one method, make that public and keep everything else out of the namespace. You could consider using a bundler like webpack or rollup, it will help with the encapsulation and testability.
    – Ignacio Catalina
    Jun 22 at 1:59










  • awesome, this was really helpful. going to start refactoring my projects right now!
    – Greg Degruy
    Jun 22 at 2:02












up vote
2
down vote



accepted







up vote
2
down vote



accepted






For how that code looks like, and based on all the methods being just namespaced within MyCompanyDynamicsClientApi, you could remove all the function() wraps between the calls, giving you something like:



MyCompanyDynamicsClientApi.runSyncMyCompanyProcess = function() 
Xrm.Utility.showProgressIndicator("Syncing MyCompany Data...");
MyCompanyDynamicsClientApi.retrieveMyCompanyConfiguration()
.then(MyCompanyDynamicsClientApi.getMyCompanyTokenAsync)
.then(MyCompanyDynamicsClientApi.getMyCompanyTasksAsync)
.then(MyCompanyDynamicsClientApi.getMyCompanyTasksInDynamicsAsync)
.then(MyCompanyDynamicsClientApi.deleteMyCompanyTasksAsync)
.then(MyCompanyDynamicsClientApi.createMyCompanyTasksAsync)
.then(MyCompanyDynamicsClientApi.openSyncMyCompanyCompleteDialog)
.catch(function (error)
return MyCompanyDynamicsClientApi.openSyncMyCompanyFailedDialog(error());
);



on top of that, I don't know why you are calling the error. It doesn't seem to be a function.



MyCompanyDynamicsClientApi.runSyncMyCompanyProcess = function() 
Xrm.Utility.showProgressIndicator("Syncing MyCompany Data...");
MyCompanyDynamicsClientApi.retrieveMyCompanyConfiguration()
.then(MyCompanyDynamicsClientApi.getMyCompanyTokenAsync)
.then(MyCompanyDynamicsClientApi.getMyCompanyTasksAsync)
.then(MyCompanyDynamicsClientApi.getMyCompanyTasksInDynamicsAsync)
.then(MyCompanyDynamicsClientApi.deleteMyCompanyTasksAsync)
.then(MyCompanyDynamicsClientApi.createMyCompanyTasksAsync)
.then(MyCompanyDynamicsClientApi.openSyncMyCompanyCompleteDialog)
.catch(MyCompanyDynamicsClientApi.openSyncMyCompanyFailedDialog);



For most of your code you can just return the promise returned by .request, and throw in the other errors, because you are already in a Promise chain, if you throw an error it will reject the promise chain and trigger the catch.



MyCompanyDynamicsClientApi.getMyCompanyTokenAsync = function() 
console.log("Trying Promise for MyCompanyTokenAsync");
// Anything failing here will make the outer promise to fail and catch to be called
var stsToken = "";
var tokenRequestParams = "grant_type=password&scope=MyCompanyApi&username=" + MyCompanyDynamicsClientApi.configuration.MyCompanyChallengeHostEmail + "&password=" + MyCompanyDynamicsClientApi.configuration.MyCompanyChallengeHostPassword;
var tokenRequestHeaders = "Authorization": "Basic TWljcm9zb2Z0RmxvdzI6W0k0JjUoXVtPZUJsTk4laGduIXo5cH1TMmp5Wlt4Xmk5" ;
var stsTokenRequestUri = "https://playMyCompanydev.azurewebsites.net/sts/connect/token";

return MyCompanyDynamicsClientApi.request("POST", stsTokenRequestUri, "application/x-www-form-urlencoded", tokenRequestHeaders, tokenRequestParams)
.then(function (response)
// Anything failing here will make this promise to fail and catch to be called
MyCompanyDynamicsClientApi.configuration.stsToken = response.access_token.toString();
)
.catch(function(error) throw new Error("Failed Promise MyCompanyDynamicsClientApi.getMyCompanyTokenAsync" + error.message); );
);
}


Other stuff not necessarily related to Promises:



If this runs in node, consider using template literals: ``



var MyCompanyConfigurationFetchXml = `
<fetch version="1.0" output-format="xml-platform" mapping="logical" distinct="true" no-lock="false">
<entity name="MyCompany_MyCompanyconfiguration">
<attribute name="MyCompany_apiurl" />
<attribute name="MyCompany_challengehostemail" />
<attribute name="MyCompany_challengehostpassword" />
<attribute name="MyCompany_clientsecret" />
<attribute name="MyCompany_MyCompanyconfigurationid" />
<filter type="and">
<condition attribute="MyCompany_apiurl" operator="not-null" />
<condition attribute="MyCompany_challengehostemail" operator="not-null" />
<condition attribute="MyCompany_challengehostpassword" operator="not-null" />
<condition attribute="MyCompany_clientsecret" operator="not-null" />
<condition attribute="statecode" operator="eq" value="0" />
</filter>
<link-entity name="MyCompany_MyCompanychallenge" from="MyCompany_MyCompanyconfigurationid" to="MyCompany_MyCompanyconfigurationid" link-type="inner" alias="MyCompany_MyCompanychallenge">
<attribute name="MyCompany_MyCompanyid" />
<attribute name="MyCompany_name" />
<attribute name="MyCompany_MyCompanyconfigurationid" />
<filter type="and">
<condition attribute="MyCompany_MyCompanyid" operator="not-null" />
<condition attribute="MyCompany_name" operator="not-null" />
<condition attribute="MyCompany_MyCompanyconfigurationid" operator="not-null" />
<condition attribute="statecode" operator="eq" value="0" />
</filter>
</link-entity>
</entity>
</fetch>`;


Extracting Constants and Mappers to separate methods:



function mapValues(response) 
var values = response.values[0];

return
MyCompanyApiUrl: values.MyCompany_apiurl.toString(),
MyCompanyChallengeHostEmail: values.MyCompany_challengehostemail.toString(),
MyCompanyChallengeHostPassword: values.MyCompany_challengehostpassword.toString(),
MyCompanyChallengeId: values["MyCompany_MyCompanychallenge.MyCompany_MyCompanyid"].toString(),
MyCompanyClientSecret: values.MyCompany_clientsecret.toString(),
;


MyCompanyDynamicsClientApi.retrieveMyCompanyConfiguration = function()
console.log("Trying Promise for MyCompanyConfiguration");
MyCompanyDynamicsClientApi.configuration.clientUrl = window.parent.Xrm.Utility.getGlobalContext().getClientUrl();

var requestPath = getCompanyConfigurationRequestPath(MyCompanyDynamicsClientApi.configuration.clientUrl);

MyCompanyDynamicsClientApi.request("GET", requestPath, "application/json")
.then(function(response)
Object.assign(MyCompanyDynamicsClientApi.configuration, mapValues(response));
)
.catch(function(error)
throw new Error("Failed Promise MyCompanyDynamicsClientApi.retrieveMyCompanyConfiguration" + error.message);
);
);
}


You might also want to split this in separate files or/and encapsulate things better so you can rely less on namespaces. It would probably make the code more readable.



Regarding the order enforcement, promises should handle that properly. If you want to parallelise some work, you can also use Promise.all().



https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Promise/all






share|improve this answer













For how that code looks like, and based on all the methods being just namespaced within MyCompanyDynamicsClientApi, you could remove all the function() wraps between the calls, giving you something like:



MyCompanyDynamicsClientApi.runSyncMyCompanyProcess = function() 
Xrm.Utility.showProgressIndicator("Syncing MyCompany Data...");
MyCompanyDynamicsClientApi.retrieveMyCompanyConfiguration()
.then(MyCompanyDynamicsClientApi.getMyCompanyTokenAsync)
.then(MyCompanyDynamicsClientApi.getMyCompanyTasksAsync)
.then(MyCompanyDynamicsClientApi.getMyCompanyTasksInDynamicsAsync)
.then(MyCompanyDynamicsClientApi.deleteMyCompanyTasksAsync)
.then(MyCompanyDynamicsClientApi.createMyCompanyTasksAsync)
.then(MyCompanyDynamicsClientApi.openSyncMyCompanyCompleteDialog)
.catch(function (error)
return MyCompanyDynamicsClientApi.openSyncMyCompanyFailedDialog(error());
);



on top of that, I don't know why you are calling the error. It doesn't seem to be a function.



MyCompanyDynamicsClientApi.runSyncMyCompanyProcess = function() 
Xrm.Utility.showProgressIndicator("Syncing MyCompany Data...");
MyCompanyDynamicsClientApi.retrieveMyCompanyConfiguration()
.then(MyCompanyDynamicsClientApi.getMyCompanyTokenAsync)
.then(MyCompanyDynamicsClientApi.getMyCompanyTasksAsync)
.then(MyCompanyDynamicsClientApi.getMyCompanyTasksInDynamicsAsync)
.then(MyCompanyDynamicsClientApi.deleteMyCompanyTasksAsync)
.then(MyCompanyDynamicsClientApi.createMyCompanyTasksAsync)
.then(MyCompanyDynamicsClientApi.openSyncMyCompanyCompleteDialog)
.catch(MyCompanyDynamicsClientApi.openSyncMyCompanyFailedDialog);



For most of your code you can just return the promise returned by .request, and throw in the other errors, because you are already in a Promise chain, if you throw an error it will reject the promise chain and trigger the catch.



MyCompanyDynamicsClientApi.getMyCompanyTokenAsync = function() 
console.log("Trying Promise for MyCompanyTokenAsync");
// Anything failing here will make the outer promise to fail and catch to be called
var stsToken = "";
var tokenRequestParams = "grant_type=password&scope=MyCompanyApi&username=" + MyCompanyDynamicsClientApi.configuration.MyCompanyChallengeHostEmail + "&password=" + MyCompanyDynamicsClientApi.configuration.MyCompanyChallengeHostPassword;
var tokenRequestHeaders = "Authorization": "Basic TWljcm9zb2Z0RmxvdzI6W0k0JjUoXVtPZUJsTk4laGduIXo5cH1TMmp5Wlt4Xmk5" ;
var stsTokenRequestUri = "https://playMyCompanydev.azurewebsites.net/sts/connect/token";

return MyCompanyDynamicsClientApi.request("POST", stsTokenRequestUri, "application/x-www-form-urlencoded", tokenRequestHeaders, tokenRequestParams)
.then(function (response)
// Anything failing here will make this promise to fail and catch to be called
MyCompanyDynamicsClientApi.configuration.stsToken = response.access_token.toString();
)
.catch(function(error) throw new Error("Failed Promise MyCompanyDynamicsClientApi.getMyCompanyTokenAsync" + error.message); );
);
}


Other stuff not necessarily related to Promises:



If this runs in node, consider using template literals: ``



var MyCompanyConfigurationFetchXml = `
<fetch version="1.0" output-format="xml-platform" mapping="logical" distinct="true" no-lock="false">
<entity name="MyCompany_MyCompanyconfiguration">
<attribute name="MyCompany_apiurl" />
<attribute name="MyCompany_challengehostemail" />
<attribute name="MyCompany_challengehostpassword" />
<attribute name="MyCompany_clientsecret" />
<attribute name="MyCompany_MyCompanyconfigurationid" />
<filter type="and">
<condition attribute="MyCompany_apiurl" operator="not-null" />
<condition attribute="MyCompany_challengehostemail" operator="not-null" />
<condition attribute="MyCompany_challengehostpassword" operator="not-null" />
<condition attribute="MyCompany_clientsecret" operator="not-null" />
<condition attribute="statecode" operator="eq" value="0" />
</filter>
<link-entity name="MyCompany_MyCompanychallenge" from="MyCompany_MyCompanyconfigurationid" to="MyCompany_MyCompanyconfigurationid" link-type="inner" alias="MyCompany_MyCompanychallenge">
<attribute name="MyCompany_MyCompanyid" />
<attribute name="MyCompany_name" />
<attribute name="MyCompany_MyCompanyconfigurationid" />
<filter type="and">
<condition attribute="MyCompany_MyCompanyid" operator="not-null" />
<condition attribute="MyCompany_name" operator="not-null" />
<condition attribute="MyCompany_MyCompanyconfigurationid" operator="not-null" />
<condition attribute="statecode" operator="eq" value="0" />
</filter>
</link-entity>
</entity>
</fetch>`;


Extracting Constants and Mappers to separate methods:



function mapValues(response) 
var values = response.values[0];

return
MyCompanyApiUrl: values.MyCompany_apiurl.toString(),
MyCompanyChallengeHostEmail: values.MyCompany_challengehostemail.toString(),
MyCompanyChallengeHostPassword: values.MyCompany_challengehostpassword.toString(),
MyCompanyChallengeId: values["MyCompany_MyCompanychallenge.MyCompany_MyCompanyid"].toString(),
MyCompanyClientSecret: values.MyCompany_clientsecret.toString(),
;


MyCompanyDynamicsClientApi.retrieveMyCompanyConfiguration = function()
console.log("Trying Promise for MyCompanyConfiguration");
MyCompanyDynamicsClientApi.configuration.clientUrl = window.parent.Xrm.Utility.getGlobalContext().getClientUrl();

var requestPath = getCompanyConfigurationRequestPath(MyCompanyDynamicsClientApi.configuration.clientUrl);

MyCompanyDynamicsClientApi.request("GET", requestPath, "application/json")
.then(function(response)
Object.assign(MyCompanyDynamicsClientApi.configuration, mapValues(response));
)
.catch(function(error)
throw new Error("Failed Promise MyCompanyDynamicsClientApi.retrieveMyCompanyConfiguration" + error.message);
);
);
}


You might also want to split this in separate files or/and encapsulate things better so you can rely less on namespaces. It would probably make the code more readable.



Regarding the order enforcement, promises should handle that properly. If you want to parallelise some work, you can also use Promise.all().



https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Promise/all







share|improve this answer













share|improve this answer



share|improve this answer











answered Jun 21 at 11:26









Ignacio Catalina

361




361











  • perfect, these are the suggestions I was looking for. I am considering using node for dev, using require/imports, then minimiaing all of my code into one Main.js file. Is this possible? In production the platform will only accept pure js in a single file.
    – Greg Degruy
    Jun 21 at 21:46










  • also, what is your opinion on name spacing in the way I have done here? I want to make sure my library is unique for the partners I work with.
    – Greg Degruy
    Jun 22 at 0:46






  • 1




    The namespacing you did is ok, but you should consider to only expose the public interface of your module. If all users can call is one method, make that public and keep everything else out of the namespace. You could consider using a bundler like webpack or rollup, it will help with the encapsulation and testability.
    – Ignacio Catalina
    Jun 22 at 1:59










  • awesome, this was really helpful. going to start refactoring my projects right now!
    – Greg Degruy
    Jun 22 at 2:02
















  • perfect, these are the suggestions I was looking for. I am considering using node for dev, using require/imports, then minimiaing all of my code into one Main.js file. Is this possible? In production the platform will only accept pure js in a single file.
    – Greg Degruy
    Jun 21 at 21:46










  • also, what is your opinion on name spacing in the way I have done here? I want to make sure my library is unique for the partners I work with.
    – Greg Degruy
    Jun 22 at 0:46






  • 1




    The namespacing you did is ok, but you should consider to only expose the public interface of your module. If all users can call is one method, make that public and keep everything else out of the namespace. You could consider using a bundler like webpack or rollup, it will help with the encapsulation and testability.
    – Ignacio Catalina
    Jun 22 at 1:59










  • awesome, this was really helpful. going to start refactoring my projects right now!
    – Greg Degruy
    Jun 22 at 2:02















perfect, these are the suggestions I was looking for. I am considering using node for dev, using require/imports, then minimiaing all of my code into one Main.js file. Is this possible? In production the platform will only accept pure js in a single file.
– Greg Degruy
Jun 21 at 21:46




perfect, these are the suggestions I was looking for. I am considering using node for dev, using require/imports, then minimiaing all of my code into one Main.js file. Is this possible? In production the platform will only accept pure js in a single file.
– Greg Degruy
Jun 21 at 21:46












also, what is your opinion on name spacing in the way I have done here? I want to make sure my library is unique for the partners I work with.
– Greg Degruy
Jun 22 at 0:46




also, what is your opinion on name spacing in the way I have done here? I want to make sure my library is unique for the partners I work with.
– Greg Degruy
Jun 22 at 0:46




1




1




The namespacing you did is ok, but you should consider to only expose the public interface of your module. If all users can call is one method, make that public and keep everything else out of the namespace. You could consider using a bundler like webpack or rollup, it will help with the encapsulation and testability.
– Ignacio Catalina
Jun 22 at 1:59




The namespacing you did is ok, but you should consider to only expose the public interface of your module. If all users can call is one method, make that public and keep everything else out of the namespace. You could consider using a bundler like webpack or rollup, it will help with the encapsulation and testability.
– Ignacio Catalina
Jun 22 at 1:59












awesome, this was really helpful. going to start refactoring my projects right now!
– Greg Degruy
Jun 22 at 2:02




awesome, this was really helpful. going to start refactoring my projects right now!
– Greg Degruy
Jun 22 at 2:02












 

draft saved


draft discarded


























 


draft saved


draft discarded














StackExchange.ready(
function ()
StackExchange.openid.initPostLogin('.new-post-login', 'https%3a%2f%2fcodereview.stackexchange.com%2fquestions%2f196127%2fenforce-request-completion-order-using-multiple-promise-chaining%23new-answer', 'question_page');

);

Post as a guest













































































Popular posts from this blog

Greedy Best First Search implementation in Rust

Function to Return a JSON Like Objects Using VBA Collections and Arrays

C++11 CLH Lock Implementation