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();
});
}
Related
I'm new to javascript and I'm struggling to return a value from what seems to be a synchronous function.
Here is the calling code:
async function testPromises() {
await Excel.run(async (context) => {
const myPromise = new Promise((resolve, reject) => {
setTimeout(() => {
findLatestRoundInLMS()
.then((value) => {
console.log('Returned data = "' + value + '"');
resolve(value);
})
}, 2000);
});
});
} // end of testPromises function
Here is the function:
// Activate Sheet and find latest round
async function findLatestRoundInLMS() {
await Excel.run(async (context) => {
const sLMS = context.workbook.worksheets.getItemOrNullObject("LMS");
// Search LMS tab for latest Round
let sourceRangeLMS;
let j;
sLMS.activate();
await context.sync();
sourceRangeLMS = sLMS.getRange("A1:CC1");
sourceRangeLMS.load("values");
await context.sync();
for (j = 3; j < 53; j += 4) {
if (sourceRangeLMS.values[0][j] > 0) {
if (sourceRangeLMS.values[0][j + 4] === 0) {
console.log("LMS index for current Round, j = " + j);
break;
} // end of IF
} // end of IF
} // end of FOR
console.log("Returning current Round Index, j = " + j);
await context.sync();
return j;
});
}
I've read lots about promises and watched some good YouTube videos but still cannot get the value to return to the calling code. Any help would be much appreciated.
I've tried several versions of promises and queues using .then coding to no avail.
Here is the link to my repo's github page, so you can properly see what I mean.
I am currently having an issue with my triviaGame function when trying to make it recursive, but it's sort of "backfiring" on me in a sense.
You'll notice after you answer the first question, everything seems fine. It goes to the next question fine. After that though, it seems like the iterations of it double? The next answer it skips 2. After that, 4. And finally the remaining 2 (adding up to 10, due to how I am iterating over them).
How might I be able to correctly iterate over a recursive function, so it correctly calls all 10 times, and then returns when it is done?
Been struggling with this for hours, and just can't seem to get it to work. My javascript code is below, sorry for any headaches that it may give you. I know I make some questionable programming decisions. Ignore some of the commented out stuff, it's not finished code yet. I'm a beginner, and hope that once I learn what's going on here it will stick with me, and I don't make a stupid mistake like this again.
const _URL = "https://opentdb.com/api.php?amount=1&category=27&type=multiple";
const _questionHTML = document.getElementById("question");
const _answerOne = document.getElementById("answer-1");
const _answerTwo = document.getElementById("answer-2");
const _answerThree = document.getElementById("answer-3");
const _answerFour = document.getElementById("answer-4");
const btns = document.querySelectorAll("button[id^=answer-]");
var runCount = 1;
var correct = 0;
// Credits to my friend Jonah for teaching me how to cache data that I get from an API call.
var triviaData = null;
async function getTrivia() {
return fetch("https://opentdb.com/api.php?amount=1&category=27&type=multiple")
.then((res) => res.json())
.then((res) => {
triviaData = res;
return res;
});
}
// anywhere I want the trivia data:
// const trivia = await getTrivia() --- makes the call, or uses the cached data
const shuffleArray = (array) => {
for (let i = array.length - 1; i > 0; i--) {
const j = Math.floor(Math.random() * (i + 1));
const temp = array[i];
array[i] = array[j];
array[j] = temp;
}
return array;
};
async function triviaGame() {
const trivia = await getTrivia();
async function appendData() {
let totalAnswers = [
...trivia.results[0].incorrect_answers,
trivia.results[0].correct_answer,
];
// Apparently I need 2 different arrays to sort them because array variables are stored by reference? Learn something new everyday I guess.
let totalAnswers2 = [...totalAnswers];
let sorted = shuffleArray(totalAnswers2);
// Ensures the proper symbol shows instead of the HTML entities
const doc = new DOMParser().parseFromString(
trivia.results[0].question,
"text/html"
);
_questionHTML.textContent = doc.documentElement.textContent;
console.log(trivia.results[0].correct_answer, "- Correct Answer");
// Appends info to the DOM
_answerOne.textContent = sorted[0];
_answerTwo.textContent = sorted[1];
_answerThree.textContent = sorted[2];
_answerFour.textContent = sorted[3];
}
async function checkAnswer() {
btns.forEach((btn) => {
btn.addEventListener("click", (event) => {
console.log(runCount);
if (event.target.textContent === trivia.results[0].correct_answer) {
event.target.style.backgroundColor = "#52D452";
// Disables all buttons after one has been clicked.
btns.forEach((btn) => {
btn.disabled = true;
});
setTimeout(() => {
if (runCount === 10) {
return;
}
runCount++;
correct++;
btns.forEach((btn) => {
btn.disabled = false;
});
btn.style.backgroundColor = "";
document.getElementById(
"amount-correct"
).textContent = `${correct}/10`;
triviaGame();
}, 2000);
} else {
event.target.style.backgroundColor = "#FF3D33";
btns.forEach((btn) => {
btn.disabled = true;
});
// document.getElementById("correct-text").textContent =
// trivia.results[0].correct_answer;
// document.getElementById("correct-answer").style.visibility =
// "visible";
setTimeout(() => {
if (runCount === 10) {
return;
}
// document.getElementById("correct-answer").style.visibility =
// "hidden";
btns.forEach((btn) => {
btn.disabled = false;
btn.style.backgroundColor = "";
});
runCount++;
triviaGame();
}, 3500);
}
});
});
}
checkAnswer();
appendData();
}
triviaGame();
Any/All responses are much appreciated and repsected. I could use any help y'all are willing to give me. The past 6 hours have been a living hell for me lol.
It's skipping questions once an answer is clicked because every time a button is clicked, another event listener is added to the button, while the original one is active:
On initial load: triviaGame() runs which makes checkAnswer() run which adds event listeners to each of the buttons.
Event listeners on buttons: 1.
Answer button is clicked, triviaGame() runs which makes checkAnswer() run which adds event listeners to each of the buttons.
Event listeners on buttons: 2.
Answer button is clicked, triviaGame() runs twice (from the 2 listeners attached) which makes checkAnswer() run twice where both invocations adds event listeners to each of the buttons.
Event listeners on buttons: 4.
etc.
To fix this, I moved the content of checkAnswer() outside of any functions so it only ever runs once. However, doing this, it loses reference to the upper scope variable trivia. To resolve this, I used the triviaData variable instead which checkAnswer() would have access to and I change references in appendData() to match this. Now, triviaGame() function only exists to call appendData() function inside it; there is little point in this so I merge the two functions together into one function, instead of two nested inside each other.
const _URL = "https://opentdb.com/api.php?amount=1&category=27&type=multiple";
const _questionHTML = document.getElementById("question");
const _answerOne = document.getElementById("answer-1");
const _answerTwo = document.getElementById("answer-2");
const _answerThree = document.getElementById("answer-3");
const _answerFour = document.getElementById("answer-4");
const btns = document.querySelectorAll("button[id^=answer-]");
var runCount = 1;
var correct = 0;
// Credits to my friend Jonah for teaching me how to cache data that I get from an API call.
var triviaData = null;
async function getTrivia() {
return fetch("https://opentdb.com/api.php?amount=1&category=27&type=multiple")
.then((res) => res.json())
.then((res) => {
triviaData = res;
return res;
});
}
// anywhere I want the trivia data:
// const trivia = await getTrivia() --- makes the call, or uses the cached data
const shuffleArray = (array) => {
for (let i = array.length - 1; i > 0; i--) {
const j = Math.floor(Math.random() * (i + 1));
const temp = array[i];
array[i] = array[j];
array[j] = temp;
}
return array;
};
async function appendData() {
triviaData = await getTrivia();
let totalAnswers = [
...triviaData.results[0].incorrect_answers,
triviaData.results[0].correct_answer,
];
// Apparently I need 2 different arrays to sort them because array variables are stored by reference? Learn something new everyday I guess.
let totalAnswers2 = [...totalAnswers];
let sorted = shuffleArray(totalAnswers2);
// Ensures the proper symbol shows instead of the HTML entities
const doc = new DOMParser().parseFromString(
triviaData.results[0].question,
"text/html"
);
_questionHTML.textContent = doc.documentElement.textContent;
console.log(triviaData.results[0].correct_answer, "- Correct Answer");
// Appends info to the DOM
_answerOne.textContent = sorted[0];
_answerTwo.textContent = sorted[1];
_answerThree.textContent = sorted[2];
_answerFour.textContent = sorted[3];
}
btns.forEach((btn) => {
btn.addEventListener("click", (event) => {
console.log(runCount);
if (event.target.textContent === triviaData.results[0].correct_answer) {
event.target.style.backgroundColor = "#52D452";
// Disables all buttons after one has been clicked.
btns.forEach((btn) => {
btn.disabled = true;
});
setTimeout(() => {
if (runCount === 10) {
return;
}
runCount++;
correct++;
btns.forEach((btn) => {
btn.disabled = false;
});
btn.style.backgroundColor = "";
document.getElementById(
"amount-correct"
).textContent = `${correct}/10`;
appendData();
}, 2000);
} else {
event.target.style.backgroundColor = "#FF3D33";
btns.forEach((btn) => {
btn.disabled = true;
});
// document.getElementById("correct-text").textContent =
// trivia.results[0].correct_answer;
// document.getElementById("correct-answer").style.visibility =
// "visible";
setTimeout(() => {
if (runCount === 10) {
return;
}
// document.getElementById("correct-answer").style.visibility =
// "hidden";
btns.forEach((btn) => {
btn.disabled = false;
btn.style.backgroundColor = "";
});
runCount++;
appendData();
}, 3500);
}
});
});
appendData();
<div id="amount-correct"></div>
<h1 id="question"></h1>
<button id="answer-1"></button>
<button id="answer-2"></button>
<button id="answer-3"></button>
<button id="answer-4"></button>
I am trying to get the numbers to log in ascending order with various delays. I tried defining the promises separately, but they automatically run which I didn't know was a thing. I then moved on to putting each of the promises into a function and calling .then on them separately, but that also did not work. I have watched multiple videos and can't seem to find one that is using setTimeout the way I am. I have tried everything I can think of and I don't see why I can't get this basic asynchronous code to work.
$('#btn2').click(() => {
myFuncingFunction();
})
function myFuncingFunction() {
console.log('1');
let first = new Promise((resolve, reject) => {
resolve(setTimeout(() => {
console.log('2');
}, 2000));
if (n === 0) {
reject();
}
}).then(() => {
let second = new Promise((resolve, reject) => {
resolve(setTimeout(() => {
console.log('3');
}, 1000));
if (n === 0) {
reject();
}
}).then(() => {
let third = new Promise((resolve, reject) => {
setTimeout(() => {
console.log('Yay')
}, 3000);
resolve();
if (n === 0) {
reject();
}
})
})
}
)
}
<script src="https://cdnjs.cloudflare.com/ajax/libs/jquery/3.3.1/jquery.min.js"></script>
<button id="btn2">Go</button>
I would create a delay function that you can use within an async counter function that you await the delay on at each step of the loop.
function delay(milliseconds = 1000){
return new Promise((resolve, reject)=>{
setTimeout(()=>{
resolve(milliseconds);
}, milliseconds);
});
}
async function counter(start, finish, stepFunc, milliseconds){
for(let i=start,m=0,d,l=finish+1; i<l; i++){
d = await delay(milliseconds);
m += d; stepFunc(i, m);
}
}
let doc, htm, bod, M, I, S, Q, mobile; // for use on other loads - forget jQuery unless you have an absolute need
addEventListener('load', ()=>{ // M makes an element based on tag - I is document.getElementById
doc = document; htm = doc.documentElement; bod = doc.body; nav = navigator; M = tag=>doc.createElement(tag); I = id=>doc.getElementById(id);
mobile = /Mobi/i.test(nav.userAgent);
S = (selector, within)=>{ // first node based on CSS selector
let w = within || doc;
return w.querySelector(selector);
}
Q = (selector, within)=>{ // array based on CSS selector
let w = within || doc;
return w.querySelectorAll(selector);
}
/* can put below on another page using a load event */
const btn2 = I('btn2');
btn2.onclick = ()=>{
counter(1, 20, (increment, milliseconds)=>{
console.log(increment, milliseconds);
}, 500).then(()=>{ // 500 milliseconds
console.log('all done');
});
}
}); // end load
<button id='btn2'>Go</button>
Note that I would use Date.now() if I was really trying to keep track of time, but this should step how you like.
I have a function that takes text and renders it to the DOM character by character to create a typewriter effect. To accomplish this, the function keeps calling itself over and over again for each character.
Sometimes I want to jump to another level and so my props.chapter changes and my textArray gets updated. However the function that was invoked before the props and textArray changed still sees the old values with each successive time it calls itself.
If I trigger the function a second time after the prop changes, the two invokations run simultaneously with one display the old data and one display the new data.
I am using React with Hooks.
Here is my function:
const typewriter = () => {
console.log(props.chapter); //will always show the props at invokation
sContents = [''];
iRow = Math.max(0, iIndex - iScrollAt);
while (iRow < iIndex) {
sContents[iIndex] += textArray[iRow++];
sContents[iIndex] = '';
}
//This is the text I render to the DOM later
setText((text) => {
text[iIndex] = <p key={`line-${iIndex}`}>{sContents[iIndex] + textArray[iIndex].substring(0, iTextPos)}<span className='caret'></span></p>;
return [...text];
});
if (iTextPos++ == iArrLength) {
iTextPos = 0;
iIndex++;
if (iIndex != textArray.length) {
setSFXon(false);
setTimeout(() => {
//Clear carat from previous line
setText((text) => {
if(text[iIndex -1]){
text[iIndex-1] = <p key={`line-${iIndex-1}`}>{sContents[iIndex-1] + textArray[iIndex-1].substring(0, iArrLength)}</p>;
}
text[iIndex] = <p key={`line-${iIndex}`}><span className='caret'></span></p>
return [...text];
});
//Write next line
iArrLength = textArray[iIndex].length; //Next line's length
//==================
//RECURSIVE CALL HERE
//==================
typewriter()
}, 1500);
}else{
//Make last line carat blink
setText((text) => {
text[iIndex-1] = <p key={`line-${iIndex-1}`}>{sContents[iIndex-1] + textArray[iIndex-1].substring(0, iArrLength)}<span className='caret blink'></span></p>;
return [...text];
});
}
} else {
//==================
// RECURSIVE CALL HERE
//==================
setTimeout(() => typewriter(), iSpeed);
}
}
I am using a node.js module that has a method without callbacks. Instead of it, has an event that fires when that method has finished. I want resolve a promise, using that event as callback securing me that method has been completed succesfully.
array.lenght on promise can be X. So, I need 'hear' X times the event to secure me that all methods has completed succesfully <-- This is not the problem, I am just telling you that I know this could happen
Event :
tf2.on('craftingComplete', function(recipe, itemsGained){
if(recipe == -1){
console.log('CRAFT FAILED')
}
else{
countOfCraft++;
console.log('Craft completed! Got a new Item #'+itemsGained);
}
})
Promise:
const craftWepsByClass = function(array, heroClass){
return new Promise(function (resolve, reject){
if(array.length < 2){
console.log('Done crafting weps of '+heroClass);
return resolve();
}
else{
for (var i = 0; i < array.length; i+=2) {
tf2.craft([array[i].id, array[i+1].id]); // <--- this is the module method witouth callback
}
return resolve(); // <---- I want resolve this, when all tf2.craft() has been completed. I need 'hear' event many times as array.length
}
})
}
At first lets promisify the crafting:
function craft(elem){
//do whatever
return Promise((resolve,reject) =>
tf2.on('craftingComplete', (recipe,itemsGained) =>
if( recipe !== -1 ){
resolve(recipe, itemsGained);
}else{
reject("unsuccessful");
}
})
);
}
So to craft multiples then, we map our array to promises and use Promise.all:
Promise.all( array.map( craft ) )
.then(_=>"all done!")
If the events will be fired in the same order as the respective craft() calls that caused them, you can use a queue:
var queue = []; // for the tf2 instance
function getNextTf2Event() {
return new Promise(resolve => {
queue.push(resolve);
});
}
tf2.on('craftingComplete', function(recipe, itemsGained) {
var resolve = queue.shift();
if (recipe == -1) {
resolve(Promise.reject(new Error('CRAFT FAILED')));
} else {
resolve(itemsGained);
}
});
function craftWepsByClass(array, heroClass) {
var promises = [];
for (var i = 1; i < array.length; i += 2) {
promises.push(getNextTf2Event().then(itemsGained => {
console.log('Craft completed! Got a new Item #'+itemsGained);
// return itemsGained;
}));
tf2.craft([array[i-1].id, array[i].id]);
}
return Promise.all(promises).then(allItemsGained => {
console.log('Done crafting weps of '+heroClass);
return …;
});
}
If you don't know anything about the order of the events, and there can be multiple concurrent calls to craftWepsByClass, you cannot avoid a global counter (i.e. one linked to the tf2 instance). The downside is that e.g. in two overlapping calls a = craftWepsByClass(…), b = craftWepsByClass() the a promise won't be resolved until all crafting of the second call is completed.
var waiting = []; // for the tf2 instance
var runningCraftings = 0;
tf2.on('craftingComplete', function(recipe, itemsGained) {
if (--runningCraftings == 0) {
for (var resolve of waiting) {
resolve();
}
waiting.length = 0;
}
if (recipe == -1) {
console.log('CRAFT FAILED')
} else {
console.log('Craft completed! Got a new Item #'+itemsGained);
}
});
function craftWepsByClass(array, heroClass) {
for (var i = 1; i < array.length; i += 2) {
runningCraftings++;
tf2.craft([array[i-1].id, array[i].id]);
}
return (runningCraftings == 0
? Promise.resolve()
: new Promise(resolve => {
waiting.push(resolve);
})
).then(() => {
console.log('Done crafting weps of '+heroClass);
});
}
Of course in both solutions you must be 100% certain that each call to craft() causes exactly one event.
You can look at the event-as-promise package. It convert events into Promise continuously until you are done with all the event processing.
When combined with async/await, you can write for-loop or while-loop easily with events. For example, we want to process data event until it return null.
const eventAsPromise = new EventAsPromise();
emitter.on('data', eventAsPromise.eventListener);
let done;
while (!done) {
const result = await eventAsPromise.upcoming();
// Some code to process the event result
process(result);
// Mark done when no more results
done = !result;
}
emitter.removeListener('data', eventAsPromise.eventListener);
If you are proficient with generator function, it may looks a bit simpler with this.
const eventAsPromise = new EventAsPromise();
emitter.on('data', eventAsPromise.eventListener);
for (let promise of eventAsPromise) {
const result = await promise;
// Some code to process the event result
process(result);
// Stop when no more results
if (!result) {
break;
}
}
emitter.removeListener('data', eventAsPromise.eventListener);
I need check if event 'craftingComplete' has fired many times as I
call tf2.craft. Doesnt matters any posible ID or if craft has failed.
I need to know if tf2.craft has finished and only why is checking
'craftingComplete' event
Given that we know i within for loop will be incremented i += 2 where i is less than .length of array, we can create a variable equal to that number before the for loop and compare i to the number within event handler
const craftWepsByClass = function(array, heroClass) {
return new Promise(function(resolve, reject) {
var countCraft = 0;
var j = 0;
var n = 0;
for (; n < array.length; n += 2);
tf2.on('craftingComplete', function(recipe, itemsGained) {
if (recipe == -1) {
console.log('CRAFT FAILED')
} else {
countOfCraft++;
console.log('Craft completed! Got a new Item #' + itemsGained);
if (j === n) {
resolve(["complete", craftCount])
}
}
})
if (array.length < 2) {
console.log('Done crafting weps of ' + heroClass);
return resolve();
} else {
try {
for (var i = 0; i < array.length; i += 2, j += 2) {
tf2.craft([array[i].id, array[i + 1].id]);
}
} catch (err) {
console.error("catch", err);
throw err
}
}
})
}
craftWepsByClass(array, heroClass)
.then(function(data) {
console.log(data[0], data[1])
})
.catch(function(err) {
console.error(".catch", err)
})