Promise.all([
seperatingDMCM(),
compileDirectMessage(),
renderingResult(),
addResultButtonListener(),
]);
I have a progress bar in my UI and I have 4 functions mentioned above each of which returns a Promise. I have a loop inside the first function seperatingDMCM(), that will handle all the my data. I want to increment the progress bar with each increment of the loop. Meaning each loop iteration should be async, so the UI will update and only afterwards the loop will iterate. When the loop has ended I want to return a promise so that the other functions will begin to execute. I am facing an issue that progress bar is not working as it suppose to and is immediately being invoked when seperatingDMCM() returns the promise () and not asynchronously. This progress bar is immediately being updated to 100% on the next page instead of live updates with small increment on the current page.
This is my updateUI function:
function startProgressBar(i, arr) {
return new Promise((resolve) => {
setTimeout(() => {
i = (i * 100) / arr.length;
console.log(i);
let elem = document.getElementById("progressBar");
if (i < 100) {
elem.innerText = i + "%";
elem.style.width = i + "%";
elem.innerHTML = i + "%";
resolve();
}
}, 0);
});
}
This is my first function where I want to update the UI per loop iteration
function seperatingDMCM() {
const contentType = "Content type";
return new Promise((resolve) => {
rowObject.forEach(async (row, index) => {
const creatingInts = () => {
console.log("CreatedInt at", index);
row["Date created (UTC)"] = ExcelDateToJSDate(
row["Date created (UTC)"]
);
if (
row[contentType] !== "DM" &&
row.hasOwnProperty("Falcon user name")
) {
publicCommentsCount++;
interaction = { row : row};
compiledInteractions.push(interaction);
interaction = {};
} else {
dmData.push(row);
}
};
startProgressBar(index, rowObject).then(creatingInts());
});
quickSort(dmData, "Falcon URL");
console.log("SORTED", dmData);
console.log(workbook);
console.log("Rows", rowObject);
resolve();
});
}
I'm using a webworker to calculate coordinates and values belonging to those places. The calculations happen in the background perfectly, keeping the DOM responsive. However, when I send the data from the webworker back to the main thread the DOM becomes unresponsive for a part of the transfer time.
My webworker (sending part):
//calculates happen before; this is the final step to give the calculated data back to the mainthread.
var processProgressGEO = {'cmd':'geoReport', 'name': 'starting transfer to main', 'current': c, 'total': polys}
postMessage(processProgressGEO);
postMessage({
'cmd':'heatmapCompleted',
'heatdata': rehashedMap,
'heatdatacount': p,
'current': c,
'total': polys,
'heatmapPeak': peakHM,
});
self.close();
The variable rehashedMap in the code snippet above is an object with numerical keys. Each key contains an array with another object in.
My mainthread (only the relevant part:)
var heatMaxforAuto = 1000000; //maximum amount of datapoints allowed in the texdata. This takes into account the spread of a singel datapoint.
async function fetchHeatData(){
return new Promise((resolve, reject) => {
var numbercruncher = new Worker('calculator.js');
console.log("Performing Second XHR request:");
var url2 = 'backend.php?datarequest=geodata'
$.ajax({
type: "GET",
url: url2,
}).then(async function(RAWGEOdata) {
data.georaw = RAWGEOdata;
numbercruncher.onmessage = async function(e){
var w = (e.data.current/e.data.total)*100+'%';
if (e.data.cmd === 'geoReport'){
console.log("HEAT: ", e.data.name, end(),'Sec.' );
}else if (e.data.cmd === 'heatmapCompleted') {
console.log("received Full heatmap data: "+end());
data.heatmap = e.data.heatdata;
console.log("heatData transfered", end());
data.heatmapMaxValue = e.data.heatmapPeak;
data.pointsInHeatmap = e.data.heatdatacount;
console.log("killing worker");
numbercruncher.terminate();
resolve(1);
}else{
throw "Unexpected command received by worker: "+ e.data.cmd;
}
}
console.log('send to worker')
numbercruncher.postMessage({'mode':'geo', 'data':data});
}).catch(function(error) {
reject(0);
throw error;
})
});
}
async function makemap(){
let heatDone = false;
if (data.texdatapoints<= heatMaxforAuto){
heatDone = await fetchHeatData();
}else{
var manualHeatMapFetcher = document.createElement("BUTTON");
var manualHeatMapFetcherText = document.createTextNode('Fetch records');
manualHeatMapFetcher.appendChild(manualHeatMapFetcherText);
manualHeatMapFetcher.id='manualHeatTriggerButton';
manualHeatMapFetcher.addEventListener("click", async function(){
$(this).toggleClass('hidden');
heatDone = await fetchHeatData();
console.log(heatDone, 'allIsDone', end());
});
document.getElementById("toggleIDheatmap").appendChild(manualHeatMapFetcher);
}
}
makemap();
The call to the end() function is needed to calculate the seconds since the start of the webworker. It returns the difference between a global set starttime and the time of calling.
What shows in my console:
HEAT: starting transfer to main 35 Sec. animator.js:44:19
received Full heatmap data: 51 animator.js:47:19
heatData transfered 51 animator.js:49:19
killing worker animator.js:52:19
1 allIsDone 51
The issue:
My DOM freezes between the start of the data transfer and the message after receiving the full heatmap data. This is the phase between the first and second message in my console. It takes 16 seconds to transfer, but the DOM only goes unresponsive once for a part of that time. Webworkers can't share data with the mainthread, so a transfer is needed.
Question:
Firstly, how to prevent the freeze of the the DOM during the onmessage phase of the webworker? Secondly, more out of curiosity: how can this freeze only occur during a part of that phase, as these are triggered by two consecutive steps with nothing going on in between?
What I tried so far:
Doing a for-loop over the rehashedMap and return key by key. This still triggers DOM freezes; shorter, but more than once. In rare occurrences it takes the tab down.
Looking for a way to buffer the onmessage phase; however, there's no such option specified in the documentation (https://developer.mozilla.org/en-US/docs/Web/API/Worker/onmessage) as compared to the postMessage phase (https://developer.mozilla.org/en-US/docs/Web/API/Worker/postMessage). Am I missing something here?
As a test I replaced the rehashedMap with an empty object; this didn't cause any freezes in the DOM. Of course, this is leaves me without access to the calculate data.
I looked at this thread on SO:Javascript WebWorker - Async/Await But I'm not sure how to compare that context to mine.
Options
It's understandable you should associate this with the web worker, but it probably doesn't have anything to do with it. I was wrong, it does. I see two possible reasons for the problem:
(We know this is not true for the OP, but may still be relevant for others.) The problem is probably that you have a lot of DOM manipulation to do once you've received the heat map. If you do that in a tight loop that never lets the main thread do anything else, the page will be unresponsive during that time.
If that's what's going on, you have to either find a way to do the DOM manipulation more quickly (sometimes that's possible, other times not) or find a way to carve it up into chunks and process each chunk separately, yielding back to the browser between chunks so that the browser can handle any pending UI work (including rendering the new elements).
You haven't included the DOM work being done with the heat map so it's not really possible to give you code to solve the problem, but the "carving up" would be done by processing a subset of the data and then using setTimeout(fn, 0) (possibly combined with requestAnimationFrame to ensure that a repaint has occurred) to schedule continuing the work (using fn) after briefly yielding to the browser.
If it's really the time spent transferring the data between the worker and the main thread, you might be able to use a transferable object for your heat map data rather than your current object, although doing so may require significantly changing your data structure. With a transferable object, you avoid copying the data from the worker to the main thread; instead, the worker transfers the actual memory to the main thread (the worker loses access to the transferable object, and the main thread gains access to it — all without copying it). For instance, the ArrayBuffer used by typed arrays (Int32Array, etc.) is transferable.
If it's really the time spent receiving the data from the worker (and from your experiments it sounds like it is), and using a transferable isn't an option (for instance, because you need the data to be in a format that isn't compatible with a transferable), the only remaining option I can see is to have the worker send the main script smaller blocks of data spaced out enough for the main thread to remain responsive. (Perhaps even sending the data as it becomes available.)
Closer look at #3
You've described an array of 1,600 entries, where each entry is an array with between 0 and "well over 7,000" objects, each with three properties (with number values). That's over 5.6 million objects. It's no surprise that cloning that data takes a fair bit of time.
Here's an example of the problem you've described:
const workerCode = document.getElementById("worker").textContent;
const workerBlob = new Blob([workerCode], { type: "text/javascript" });
const workerUrl = (window.webkitURL || window.URL).createObjectURL(workerBlob);
const worker = new Worker(workerUrl);
worker.addEventListener("message", ({data}) => {
if ((data && data.action) === "data") {
console.log(Date.now(), `Received ${data.array.length} rows`);
if (data.done) {
stopSpinning();
}
}
});
document.getElementById("btn-go").addEventListener("click", () => {
console.log(Date.now(), "requesting data");
startSpinning();
worker.postMessage({action: "go"});
});
const spinner = document.getElementById("spinner");
const states = [..."▁▂▃▄▅▆▇█▇▆▅▄▃▂▁"];
let stateIndex = 0;
let spinHandle = 0;
let maxDelay = 0;
let intervalStart = 0;
function startSpinning() {
if (spinner) {
cancelAnimationFrame(spinHandle);
maxDelay = 0;
queueUpdate();
}
}
function queueUpdate() {
intervalStart = Date.now();
spinHandle = requestAnimationFrame(() => {
updateMax();
spinner.textContent = states[stateIndex];
stateIndex = (stateIndex + 1) % states.length;
if (spinHandle) {
queueUpdate();
}
});
}
function stopSpinning() {
updateMax();
cancelAnimationFrame(spinHandle);
spinHandle = 0;
if (spinner) {
spinner.textContent = "Done";
console.log(`Max delay between frames: ${maxDelay}ms`);
}
}
function updateMax() {
if (intervalStart !== 0) {
const elapsed = Date.now() - intervalStart;
if (elapsed > maxDelay) {
maxDelay = elapsed;
}
}
}
<div>(Look in the real browser console.)</div>
<input type="button" id="btn-go" value="Go">
<div id="spinner"></div>
<script type="worker" id="worker">
const r = Math.random;
self.addEventListener("message", ({data}) => {
if ((data && data.action) === "go") {
console.log(Date.now(), "building data");
const array = Array.from({length: 1600}, () =>
Array.from({length: Math.floor(r() * 7000)}, () => ({lat: r(), lng: r(), value: r()}))
);
console.log(Date.now(), "data built");
console.log(Date.now(), "sending data");
postMessage({
action: "data",
array,
done: true
});
console.log(Date.now(), "data sent");
}
});
</script>
Here's an example of the worker sending the data in chunks as fast as it can but in separate messages. It makes the page responsive (though still jittery) when receiving the data:
const workerCode = document.getElementById("worker").textContent;
const workerBlob = new Blob([workerCode], { type: "text/javascript" });
const workerUrl = (window.webkitURL || window.URL).createObjectURL(workerBlob);
const worker = new Worker(workerUrl);
let array = null;
let clockTimeStart = 0;
worker.addEventListener("message", ({data}) => {
if ((data && data.action) === "data") {
if (clockTimeStart === 0) {
clockTimeStart = Date.now();
console.log(Date.now(), "Receiving data");
}
array.push(...data.array);
if (data.done) {
console.log(Date.now(), `Received ${array.length} row(s) in total, clock time to receive data: ${Date.now() - clockTimeStart}ms`);
stopSpinning();
}
}
});
document.getElementById("btn-go").addEventListener("click", () => {
console.log(Date.now(), "requesting data");
array = [];
clockTimeStart = 0;
startSpinning();
worker.postMessage({action: "go"});
});
const spinner = document.getElementById("spinner");
const states = [..."▁▂▃▄▅▆▇█▇▆▅▄▃▂▁"];
let stateIndex = 0;
let spinHandle = 0;
let maxDelay = 0;
let intervalStart = 0;
function startSpinning() {
if (spinner) {
cancelAnimationFrame(spinHandle);
maxDelay = 0;
queueUpdate();
}
}
function queueUpdate() {
intervalStart = Date.now();
spinHandle = requestAnimationFrame(() => {
updateMax();
spinner.textContent = states[stateIndex];
stateIndex = (stateIndex + 1) % states.length;
if (spinHandle) {
queueUpdate();
}
});
}
function stopSpinning() {
updateMax();
cancelAnimationFrame(spinHandle);
spinHandle = 0;
if (spinner) {
spinner.textContent = "Done";
console.log(`Max delay between frames: ${maxDelay}ms`);
}
}
function updateMax() {
if (intervalStart !== 0) {
const elapsed = Date.now() - intervalStart;
if (elapsed > maxDelay) {
maxDelay = elapsed;
}
}
}
<div>(Look in the real browser console.)</div>
<input type="button" id="btn-go" value="Go">
<div id="spinner"></div>
<script type="worker" id="worker">
const r = Math.random;
self.addEventListener("message", ({data}) => {
if ((data && data.action) === "go") {
console.log(Date.now(), "building data");
const array = Array.from({length: 1600}, () =>
Array.from({length: Math.floor(r() * 7000)}, () => ({lat: r(), lng: r(), value: r()}))
);
console.log(Date.now(), "data built");
const total = 1600;
const chunks = 100;
const perChunk = total / chunks;
if (perChunk !== Math.floor(perChunk)) {
throw new Error(`total = ${total}, chunks = ${chunks}, total / chunks has remainder`);
}
for (let n = 0; n < chunks; ++n) {
postMessage({
action: "data",
array: array.slice(n * perChunk, (n + 1) * perChunk),
done: n === chunks - 1
});
}
}
});
</script>
Naturally it's a tradeoff. The total clock time spent receiving the data is longer the smaller the chunks; the smaller the chunks, the less jittery the page is. Here's really small chunks (sending each of the 1,600 arrays separately):
const workerCode = document.getElementById("worker").textContent;
const workerBlob = new Blob([workerCode], { type: "text/javascript" });
const workerUrl = (window.webkitURL || window.URL).createObjectURL(workerBlob);
const worker = new Worker(workerUrl);
let array = null;
let clockTimeStart = 0;
worker.addEventListener("message", ({data}) => {
if ((data && data.action) === "data") {
if (clockTimeStart === 0) {
clockTimeStart = Date.now();
}
array.push(data.array);
if (data.done) {
console.log(`Received ${array.length} row(s) in total, clock time to receive data: ${Date.now() - clockTimeStart}ms`);
stopSpinning();
}
}
});
document.getElementById("btn-go").addEventListener("click", () => {
console.log(Date.now(), "requesting data");
array = [];
clockTimeStart = 0;
startSpinning();
worker.postMessage({action: "go"});
});
const spinner = document.getElementById("spinner");
const states = [..."▁▂▃▄▅▆▇█▇▆▅▄▃▂▁"];
let stateIndex = 0;
let spinHandle = 0;
let maxDelay = 0;
let intervalStart = 0;
function startSpinning() {
if (spinner) {
cancelAnimationFrame(spinHandle);
maxDelay = 0;
queueUpdate();
}
}
function queueUpdate() {
intervalStart = Date.now();
spinHandle = requestAnimationFrame(() => {
updateMax();
spinner.textContent = states[stateIndex];
stateIndex = (stateIndex + 1) % states.length;
if (spinHandle) {
queueUpdate();
}
});
}
function stopSpinning() {
updateMax();
cancelAnimationFrame(spinHandle);
spinHandle = 0;
if (spinner) {
spinner.textContent = "Done";
console.log(`Max delay between frames: ${maxDelay}ms`);
}
}
function updateMax() {
if (intervalStart !== 0) {
const elapsed = Date.now() - intervalStart;
if (elapsed > maxDelay) {
maxDelay = elapsed;
}
}
}
<div>(Look in the real browser console.)</div>
<input type="button" id="btn-go" value="Go">
<div id="spinner"></div>
<script type="worker" id="worker">
const r = Math.random;
self.addEventListener("message", ({data}) => {
if ((data && data.action) === "go") {
console.log(Date.now(), "building data");
const array = Array.from({length: 1600}, () =>
Array.from({length: Math.floor(r() * 7000)}, () => ({lat: r(), lng: r(), value: r()}))
);
console.log(Date.now(), "data built");
array.forEach((chunk, index) => {
postMessage({
action: "data",
array: chunk,
done: index === array.length - 1
});
});
}
});
</script>
That's building all the data and then sending it, but if building the data times time, interspersing building and sending it may make the page responsiveness smoother, particularly if you can send the inner arrays in smaller pieces (as even sending ~7,000 objects still causes jitter, as we can see in the last example above).
Combining #2 and #3
Each entry in your main array is an array of objects with three numeric properties. We could instead send Float64Arrays with those values in lat/lng/value order, using the fact they're transferable:
const workerCode = document.getElementById("worker").textContent;
const workerBlob = new Blob([workerCode], { type: "text/javascript" });
const workerUrl = (window.webkitURL || window.URL).createObjectURL(workerBlob);
const worker = new Worker(workerUrl);
let array = null;
let clockTimeStart = 0;
worker.addEventListener("message", ({data}) => {
if ((data && data.action) === "data") {
if (clockTimeStart === 0) {
clockTimeStart = Date.now();
}
const nums = data.array;
let n = 0;
const entry = [];
while (n < nums.length) {
entry.push({
lat: nums[n++],
lng: nums[n++],
value: nums[n++]
});
}
array.push(entry);
if (data.done) {
console.log(Date.now(), `Received ${array.length} row(s) in total, clock time to receive data: ${Date.now() - clockTimeStart}ms`);
stopSpinning();
}
}
});
document.getElementById("btn-go").addEventListener("click", () => {
console.log(Date.now(), "requesting data");
array = [];
clockTimeStart = 0;
startSpinning();
worker.postMessage({action: "go"});
});
const spinner = document.getElementById("spinner");
const states = [..."▁▂▃▄▅▆▇█▇▆▅▄▃▂▁"];
let stateIndex = 0;
let spinHandle = 0;
let maxDelay = 0;
let intervalStart = 0;
function startSpinning() {
if (spinner) {
cancelAnimationFrame(spinHandle);
maxDelay = 0;
queueUpdate();
}
}
function queueUpdate() {
intervalStart = Date.now();
spinHandle = requestAnimationFrame(() => {
updateMax();
spinner.textContent = states[stateIndex];
stateIndex = (stateIndex + 1) % states.length;
if (spinHandle) {
queueUpdate();
}
});
}
function stopSpinning() {
updateMax();
cancelAnimationFrame(spinHandle);
spinHandle = 0;
if (spinner) {
spinner.textContent = "Done";
console.log(`Max delay between frames: ${maxDelay}ms`);
}
}
function updateMax() {
if (intervalStart !== 0) {
const elapsed = Date.now() - intervalStart;
if (elapsed > maxDelay) {
maxDelay = elapsed;
}
}
}
<div>(Look in the real browser console.)</div>
<input type="button" id="btn-go" value="Go">
<div id="spinner"></div>
<script type="worker" id="worker">
const r = Math.random;
self.addEventListener("message", ({data}) => {
if ((data && data.action) === "go") {
for (let n = 0; n < 1600; ++n) {
const nums = Float64Array.from(
{length: Math.floor(r() * 7000) * 3},
() => r()
);
postMessage({
action: "data",
array: nums,
done: n === 1600 - 1
}, [nums.buffer]);
}
}
});
</script>
That dramatically reduces the clock time to receive the data, while keeping the UI fairly responsive.
happyCustomers = 0;
followers = 0;
awardsWinning = 0;
photosTaken = 0;
arrayOfImages = [];
constructor() { }
ngOnInit() {
Observable.interval(400).subscribe(x => {
this.happyCustomers = this.happyCustomers + 1;
});
Observable.interval(200).subscribe(x => {
this.followers = this.followers + 1;
});
Observable.interval(700).subscribe(x => {
this.awardsWinning = this.awardsWinning + 1;
});
Observable.interval(300).subscribe(x => {
this.photosTaken = this.photosTaken + 1;
});
}
here i am adding + 1 evrytime and it does not stop, when i reach a certain data count it should stop the count.
Use take method.
take method takes the first count values from the source, then completes.
Call it before subscription.
const certainNumber = 10;
Observable.interval(400)
.take(certainNumber)
.subscribe(_ => this.happyCustomers++);
If you have a hardcoded value, you can use the take() command.
Example :
Observable.interval(400)
.take(500)
.subscribe(x => {
this.happyCustomers = this.happyCustomers + 1;
});
The above code will stop after 500 events have been emitted.
See http://reactivex.io/rxjs/class/es6/Observable.js~Observable.html#instance-method-take
In the following example, I am simulating a scenario where an user can trigger some actions, every action is a asynchronous operation (wrapped in a promise) which can be resolved with a random timing.
The list of actions is also dynamic, an user can trigger only one actions or many in a span of time.
I would need:
Keep the promise resolution sequential, keeping in consideration that a full list of actions is not known in advance.
I can use ES6 and Browser native Promises
To test the example, click several times (with variable frequency) the button.
(function (window) {
document.addEventListener('DOMContentLoaded', e => {
let logActionsElm = document.getElementById('logActions');
let logOperationsElm = document.getElementById('logOperations');
let logCounterElm = document.getElementById('logCounter');
let btnActionElm = document.getElementById('btnAction');
let actionCounter = 0;
let operationDurations = [1000, 2000, 3000, 4000, 5000, 6000, 7000, 8000, 9000];
let getRandomNumber = (min, max) => Math.floor(Math.random() * (max - 0 + min)) + min;
let getRandomOperationDuration = x => operationDurations[getRandomNumber(0, 8)];
let promises = [];
let createAction = (id, duration) => {
logActionsElm.innerHTML += `${id} action_start_duration: ${duration} --------- ${id}<br>`;
let promiseAction = new Promise((resolve, reject) => {
setTimeout(e => {
logActionsElm.innerHTML += `${id} action_end___duration: ${duration} --------- ${id}<br>`;
}, duration);
});
};
let createOperation = x => {
actionCounter++;
let duration = getRandomOperationDuration() / 10;
createAction(actionCounter, duration);
//logActionsElm.innerHTML += `action ${actionCounter} created will resolve after ${duration}<br>`;
};
btnActionElm.addEventListener('click', e => {
createOperation();
});
var counter = 0;
setInterval(x => {
if (counter >= 20) {
return;
}
counter++;
logCounterElm.innerHTML += `${counter} second <br>`;
}, 1000);
});
})(window);
body {
font-size: 1.5em;
font-family: 'Courier New';
}
#logCounter {
position: fixed;
top: 0;
right: 0;
margin: 2em;
}
<button id="btnAction">Triger and action</button>
<div id="logActions"></div>
<div id="logOperations"></div>
<div id="logCounter"></div>
You could append new action functions to a single promise to ensure actions aren't evaluated until after all previous actions have resolved:
let queue = Promise.resolve();
let userActionIdCounter = 0;
function queueAction(fn) {
queue = queue.then(fn);
return queue;
}
function userAction() {
return new Promise((resolve) => {
const seconds = Math.ceil(Math.random() * 5);
const actionId = userActionIdCounter;
userActionIdCounter += 1;
console.log('User action', userActionIdCounter, 'started');
setTimeout(() => {
console.log('User action', userActionIdCounter, 'complete');
resolve();
}, seconds * 1000);
});
}
document.getElementById('action').addEventListener('click', () => {
queueAction(userAction);
});
<button id='action'>Trigger action</button>
You should NOT create your own solution. Instead, select from one of the approximately 158 existing solutions here https://www.npmjs.com/search?q=promise+queue
When I get a request, I want it to generate a 4-character code, then check if it already exists in the database. If it does, then generate a new code. If not, add it and move on. This is what I have so far:
var code = "";
var codeFree = false;
while (! codeFree) {
var chars = "ABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789";
var code = "";
for (var i = 0; i < 4; i++) {
var rand = Math.floor(Math.random() * chars.length);
console.log(rand);
code += chars.charAt(rand);
}
console.log("Code: %s generated.", code);
client.execute("select * from codes where code=" + code, function(err, result) {
if (! err) {
if (result.rows.length > 0) {
codeFree = false;
} else {
codeFree = true;
}
} else {
console.log('DB ERR: %s', err);
}
console.log(codeFree);
});
console.log('here');
}
This does not do nearly what I want it to do. How can I handle something like this?
You are doing an async task.
When you have an asyncronous task inside your procedure, you need to have a callback function which is going to be called with the desired value as its argument.
When you found the free code, you call the function and passing the code as its argument, otherwise, you call the getFreeCode function again and passing the same callback to it. Although you might consider cases when an error happens. If your the db call fails, your callback would never get called. It is better to use a throw/catch mechanism or passing another argument for error to your callback.
You can achieve what you need to do by doing it this way:
function getFreeCode(callback) {
var chars = "ABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789";
var code = "";
for (var i = 0; i < 4; i++) {
var rand = Math.floor(Math.random() * chars.length);
console.log(rand);
code += chars.charAt(rand);
}
console.log("Code: %s generated.", code);
client.execute("select * from codes where code="+code, function(err, result) {
if(!err) {
if(result.rows.length > 0) {
getFreeCode(callback);
} else {
callback(code);
}
}else {
console.log('DB ERR: %s', err);
}
console.log(codeFree);
});
console.log('here');
}
// in your main:
getFreeCode(function (code) {
console.log(' this code was free: ' + code)
})
I recommend you look into two alternatives to help deal with asynchronous code.
node generator functions using the 'yield' keyword
promises
Using generators requires running a recent version of node with the --harmony flag. The reason I recommend generators is because you can write code that flows the way you expect.
var x = yield asyncFunction();
console.log('x = ' + x);
The previous code will get the value of x before logging x.
Without yielding the console.log would write out x before the async function was finished getting the value for x.
Your code could look like this with generators:
var client = {
execute: function (query) {
var timesRan = 0;
var result = [];
return function () {
return setTimeout(function () {
result = ++timesRan < 4 ? ['length_will_be_1'] : [];
return result;
},1);
};
}
};
function* checkCode () {
var code;
var codeFree = false;
while(!codeFree) {
var chars = "ABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789";
code = "";
for (var i = 0; i < 4; i++) {
var rand = Math.floor(Math.random() * chars.length);
console.log(rand);
code += chars.charAt(rand);
}
console.log("Code: %s generated.", code);
try {
var result = yield client.execute("select * from codes where code="+code);
codeFree = result.rows.length > 0 ? false : true;
}catch(e) {
console.log('DB ERR: %s', err);
} finally {
console.log(codeFree);
}
console.log('here');
}
}
checkCode().next();
You would leave off the client object. I only added that to make a working example that fakes an async call.
If you have to use an older version of node or do not like the yield syntax then promises could be a worthy option.
There are many promise libraries. The reason I recommend promises is that you can write code that flows the way you expect:
asyncGetX()
.then(function (x) {
console.log('x: ' + x);
});
The previous code will get the value of x before logging x.
It also lets you chain async functions and runs them in order:
asyncFunction1()
.then(function (result) {
return asyncFunction2(result)
})
.then(function (x) { /* <-- x is the return value from asyncFunction2 which used the result value of asyncFunction1 */
console.log('x: ' + x);
});
Your code could look like this with the 'q' promise library:
var Q = require('q');
var client = {
timesRan: 0,
execute: function (query, callback) {
var self = this;
var result = {};
setTimeout(function () {
console.log('self.timesRan: ' + self.timesRan);
result.rows = ++self.timesRan < 4 ? ['length = 1'] : [];
callback(null, result);
},1);
}
};
function checkCode () {
var deferred = Q.defer();
var codeFree = false;
var chars = "ABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789";
var code = "";
for (var i = 0; i < 4; i++) {
var rand = Math.floor(Math.random() * chars.length);
console.log('rand: %s', rand);
code += chars.charAt(rand);
}
console.log("Code: %s generated.", code);
client.execute("select * from codes where code="+code, function(err, result) {
console.log('err: '+err+', result: ' + JSON.stringify(result));
console.log('result.rows.length: ' + result.rows.length);
if(!err) {
if(result.rows.length > 0) {
codeFree = false;
console.log('result.rows: %s, codeFree: %s', result.rows, codeFree);
checkCode();
} else {
codeFree = true;
console.log('line 36: codeFree: ' + codeFree);
deferred.resolve(code);
}
}else {
console.log('DB ERR: %s', err);
deferred.reject(err);
}
console.log(codeFree);
});
console.log('waiting for promise');
return deferred.promise;
}
checkCode()
.then(function (code) {
console.log('success with code: ' + code);
})
.fail(function(err) {
console.log('failure, err: ' + err);
});
Also omit the client object here. I only added that to make a working example that fakes an async call.
Promises and generators definitely take some time to get used to. It's worth it because they make the code a lot easier to follow in the end than code written with nested callbacks.