JavaScript is asynchronous and this enables us to create websites that can respond to user interaction while doing other work, a non-freezing experience. An asynchronous behaviour can be a challenge when you want to make sure that functions is excecuted in a certain order or when you only want to execute a function if the previous call has finished.
You can use promises and await in JavaScript to gain control over your code and to be able to wait for functions to finish their executions. A function that returns a promise can be awaited.
This code has been tested and is working with Google Chrome (75.0.3770.100), Mozilla Firefox (67.0.4) and Microsoft Edge (42.17134.1.0), without any polyfill. It works in Internet Explorer (11.829.17134.0) with polyfills for XMLHttpRequest and Promise. If you want to support older browsers, check out our post on transpilation and polyfilling of JavaScript.
Funtion to be awaited
The following JavaScript function are supposed to be called in intervals and it can be awaited, it is marked as async and returns a promise. This function returns resolve on success and reject on an error.
async function getLog(log_name)
{
// Return a promise
return new Promise((resolve, reject) => {
// Create form data
var form_data = new FormData();
form_data.append('LogName', log_name);
var xhr = new XMLHttpRequest();
xhr.open('POST', 'https://mysite.com/api/getlog', true);
xhr.onload = function () {
// Check if the response is successful
if (xhr.status === 200)
{
// Return a success response
resolve(xhr.response);
}
else
{
// Return a reject response
reject(xhr.status + " - " + xhr.statusText);
}
};
xhr.onerror = function ()
{
// Return a reject response
reject(xhr.status + " - " + xhr.statusText);
};
xhr.send(form_data);
});
} // End of the getLog method
Wait for function to finish excecution
The following function will call the getLog method in intervals while waiting for a long running task to finish. The getLog method is called with setTimeout until the finished boolean is true. This code makes sure that we do not call getLog more times than necessary.
async function syncFortnox(method)
{
// Check if a lock is taken
if (fortnoxlock.value === 'true') {
toastr['error']('Du måste vänta tills den pågående processen blir klar.');
return;
}
// Hide containers
var collection = document.getElementsByClassName('hideable');
for (var i = 0; i < collection.length; i++) {
annytab.effects.slideUp(collection[i], 500);
}
// Lock the process
fortnoxlock.value = 'true';
// Get a log window reference
var container = document.getElementById('log-window');
// Start a loading animation
//$('#log-window').html('<div class="annytab-basic-loading-container"><i class="fas fa-spinner fa-pulse fa-4x fa-fw"></i><div class="annytab-basic-loading-text">Arbetar ...</div></div>');
container.innerHTML = '<div style="color:#4CAF50;"><i class="fas fa-spinner fa-pulse fa-fw"></i> Arbetar ...</div>';
// Get fortnox api values
var nox_api_values = new Object();
nox_api_values.AccessToken = document.getElementsByName('txtAccessToken')[0].value;
nox_api_values.PriceList = document.getElementsByName('txtPriceList')[0].value;
nox_api_values.PenaltyInterest = parseInt(document.getElementsByName('txtPenaltyInterest')[0].value) / 100;
nox_api_values.SalesVatTypeSE = document.getElementsByName('selectSalesVatTypeSE')[0].value;
nox_api_values.SalesAccountSE25 = document.getElementsByName('txtSalesAccountSE25')[0].value;
nox_api_values.SalesAccountSE12 = document.getElementsByName('txtSalesAccountSE12')[0].value;
nox_api_values.SalesAccountSE6 = document.getElementsByName('txtSalesAccountSE6')[0].value;
nox_api_values.SalesAccountSE0 = document.getElementsByName('txtSalesAccountSE0')[0].value;
nox_api_values.SalesAccountSEREVERSEDVAT = document.getElementsByName('txtSalesAccountSEREVERESEDVAT')[0].value;
nox_api_values.SalesAccountEUVAT = document.getElementsByName('txtSalesAccountEUVAT')[0].value;
nox_api_values.SalesAccountEUREVERSEDVAT = document.getElementsByName('txtSalesAccountEUREVERESEDVAT')[0].value;
nox_api_values.SalesAccountEXPORT = document.getElementsByName('txtSalesAccountEXPORT')[0].value;
nox_api_values.PurchaseAccount = document.getElementsByName('txtPurchaseAccount')[0].value;
nox_api_values.StockArticle = (document.getElementsByName('cbStockArticle')[0].value === 'true');
nox_api_values.StockAccount = document.getElementsByName('txtStockAccount')[0].value;
nox_api_values.StockChangeAccount = document.getElementsByName('txtStockChangeAccount')[0].value;
nox_api_values.OnlyAllowTrustedSenders = (document.getElementsByName('cbTrustedSenders')[0].value === 'true');
// Get a guid
var guid = getGuid();
// Get the log file name
var log_name = dox_api_values.ApiEmail + "/" + guid + ".log";
// Get logs every second
var finished = false;
setTimeout(async function run()
{
var response = '';
try
{
// Get log information and wait for a response
response = await getLog(log_name);
}
catch (err)
{
console.log(err);
}
if (finished === true)
{
// Print to log window and return
printToLogWindow(container, response, finished);
return;
}
else
{
// Print to log window and run again
printToLogWindow(container, response, finished);
setTimeout(run, 500);
}
}, 1000);
// Create form data
var form_data = new FormData();
form_data.append('Guid', guid);
form_data.append('DoxservrApiValues', JSON.stringify(dox_api_values));
form_data.append('FortnoxApiValues', JSON.stringify(nox_api_values));
var xhr = new XMLHttpRequest();
xhr.open('POST', 'https://mysite.com/api/' + method, true);
xhr.onload = function () {
// Check if the response is successful
if (xhr.status === 200) {
// Mark the process as finished
finished = true;
//clearTimeout(timeout);
}
else
{
// Output error information
toastr['error'](xhr.status + " - " + xhr.statusText);
}
// Release the lock
fortnoxlock.value = 'false';
};
xhr.send(form_data);
} // End of the syncFortnox method