I am very new to Javascript and I am trying to utilize BestBuy's api to grab data on a specific sku number every 3 seconds.
The call to the api works as expected on its own but when I move the call inside a while loop, I do not get anything and the console.log in the function is not hit.
Not sure where to go from here and I am starting wonder if this api call can only be called by itself.
Would appreciate any help.
var bby = require('bestbuy')('<Actual BestBuy Api key in original code>');
var isSoldOut = true;
while(isSoldOut)
{
console.log("HERE");
bby.products(6465789,{show:'sku,name,salePrice,onlineAvailability,inStorePickup,onlineAvailabilityUpdateDate'}).then(function(data){
console.log(data);
if (data['onlineAvailability'] == false)
{
isSoldOut = false;
console.log(isSoldOut);
}
});
wait(3000);
}
function wait(ms)
{
var d = new Date();
var d2 = null;
do { d2 = new Date(); }
while(d2-d < ms);
}
Your while loops are constantly blocking the thread, so your callback function can't run.
Instead of the while loops, you can use an interval:
var bby = require('bestbuy')('<Actual BestBuy Api key in original code>');
function check() {
console.log("HERE");
bby.products(6465789,{show:'sku,name,salePrice,onlineAvailability,inStorePickup,onlineAvailabilityUpdateDate'}).then(function(data){
console.log(data);
if (data['onlineAvailability'] == false)
{
clearInterval(loop);
console.log("In Stock");
}
});
}
const loop = setInterval(check, 3000);
Even cleaner may be using async/await to wait 3 seconds after receiving each response:
var bby = require('bestbuy')('<Actual BestBuy Api key in original code>');
const wait = require('util').promisify(setTimeout);
async function check() {
console.log("HERE");
const data = await bby.products(6465789,{show:'sku,name,salePrice,onlineAvailability,inStorePickup,onlineAvailabilityUpdateDate'});
console.log(data);
if (data['onlineAvailability'] == false)
{
console.log("In Stock");
return;
}
await wait(3000);
}
check();
There are several points to improve your code:
Use setInterval to execute code in regular interval, instead of using spin lock in a while loop
Use async/await to improve readability
Instead of checking condition == false, simply check !condition
Instead of doing if(!condition){x = false}, simply do x = !condition
Use dot notation instead of bracket notation to access object property if the property name is not variable. This can take advantage of intellisense of IDE if the object shape is properly documented and reduce typo.
const t = setInterval(async () =>{
const data = await bby.products(6465789, { show: 'sku,name,salePrice,onlineAvailability,inStorePickup,onlineAvailabilityUpdateDate' });
console.log(data);
isSoldOut = !data.onlineAvailability;
console.log(isSoldOut);
if(isSoldOut)
{
clearInterval(t);
}
},3000);
Related
I want to be able to change the value of a global variable when it is being used by a function as a parameter.
My javascript:
function playAudio(audioFile, canPlay) {
if (canPlay < 2 && audioFile.paused) {
canPlay = canPlay + 1;
audioFile.play();
} else {
if (canPlay >= 2) {
alert("This audio has already been played twice.");
} else {
alert("Please wait for the audio to finish playing.");
};
};
};
const btnPitch01 = document.getElementById("btnPitch01");
const audioFilePitch01 = new Audio("../aud/Pitch01.wav");
var canPlayPitch01 = 0;
btnPitch01.addEventListener("click", function() {
playAudio(audioFilePitch01, canPlayPitch01);
});
My HTML:
<body>
<button id="btnPitch01">Play Pitch01</button>
<button id="btnPitch02">Play Pitch02</button>
<script src="js/js-master.js"></script>
</body>
My scenario:
I'm building a Musical Aptitude Test for personal use that won't be hosted online. There are going to be hundreds of buttons each corresponding to their own audio files. Each audio file may only be played twice and no more than that. Buttons may not be pressed while their corresponding audio files are already playing.
All of that was working completely fine, until I optimised the function to use parameters. I know this would be good to avoid copy-pasting the same function hundreds of times, but it has broken the solution I used to prevent the audio from being played more than once. The "canPlayPitch01" variable, when it is being used as a parameter, no longer gets incremented, and therefore makes the [if (canPlay < 2)] useless.
How would I go about solving this? Even if it is bad coding practise, I would prefer to keep using the method I'm currently using, because I think it is a very logical one.
I'm a beginner and know very little, so please forgive any mistakes or poor coding practises. I welcome corrections and tips.
Thank you very much!
It's not possible, since variables are passed by value, not by reference. You should return the new value, and the caller should assign it to the variable.
function playAudio(audioFile, canPlay) {
if (canPlay < 2 && audioFile.paused) {
canPlay = canPlay + 1;
audioFile.play();
} else {
if (canPlay >= 2) {
alert("This audio has already been played twice.");
} else {
alert("Please wait for the audio to finish playing.");
};
};
return canPlay;
};
const btnPitch01 = document.getElementById("btnPitch01");
const audioFilePitch01 = new Audio("../aud/Pitch01.wav");
var canPlayPitch01 = 0;
btnPitch01.addEventListener("click", function() {
canPlayPitch01 = playAudio(audioFilePitch01, canPlayPitch01);
});
A little improvement of the data will fix the stated problem and probably have quite a few side benefits elsewhere in the code.
Your data looks like this:
const btnPitch01 = document.getElementById("btnPitch01");
const audioFilePitch01 = new Audio("../aud/Pitch01.wav");
var canPlayPitch01 = 0;
// and, judging by the naming used, there's probably more like this:
const btnPitch02 = document.getElementById("btnPitch02");
const audioFilePitch02 = new Audio("../aud/Pitch02.wav");
var canPlayPitch02 = 0;
// and so on
Now consider that global data looking like this:
const model = {
btnPitch01: {
canPlay: 0,
el: document.getElementById("btnPitch01"),
audioFile: new Audio("../aud/Pitch01.wav")
},
btnPitch02: { /* and so on */ }
}
Your event listener(s) can say:
btnPitch01.addEventListener("click", function(event) {
// notice how (if this is all that's done here) we can shrink this even further later
playAudio(event);
});
And your playAudio function can have a side-effect on the data:
function playAudio(event) {
// here's how we get from the button to the model item
const item = model[event.target.id];
if (item.canPlay < 2 && item.audioFile.paused) {
item.canPlay++;
item.audioFile.play();
} else {
if (item.canPlay >= 2) {
alert("This audio has already been played twice.");
} else {
alert("Please wait for the audio to finish playing.");
};
};
};
Side note: the model can probably be built in code...
// you can automate this even more using String padStart() on 1,2,3...
const baseIds = [ '01', '02', ... ];
const model = Object.fromEntries(
baseIds.map(baseId => {
const id = `btnPitch${baseId}`;
const value = {
canPlay: 0,
el: document.getElementById(id),
audioFile: new Audio(`../aud/Pitch${baseId}.wav`)
}
return [id, value];
})
);
// you can build the event listeners in a loop, too
// (or in the loop above)
Object.values(model).forEach(value => {
value.el.addEventListener("click", playAudio)
})
below is an example of the function.
btnPitch01.addEventListener("click", function() {
if ( this.dataset.numberOfPlays >= this.dataset.allowedNumberOfPlays ) return;
playAudio(audioFilePitch01, canPlayPitch01);
this.dataset.numberOfPlays++;
});
you would want to select all of your buttons and assign this to them after your html is loaded.
https://developer.mozilla.org/en-US/docs/Web/API/Document/getElementsByClassName
const listOfButtons = document.getElementsByClassName('pitchButton');
listOfButtons.forEach( item => {
item.addEventListener("click", () => {
if ( this.dataset.numberOfPlays >= this.dataset.allowedNumberOfPlays ) return;
playAudio("audioFilePitch" + this.id);
this.dataset.numberOfPlays++;
});
I have a process in my code where I need to get a list of technician drive times. I use the Google Maps API to get the driving time between the origin and destination. As most of you know, the API requires to have a timeout of roughly 1 second or more to work without generating errors. I have created a recursive function to retrieve the list of times that I need using a setTimeout within the method, like so:
function GetTechDriveTimes(info, destAddress) {
let techs = this.state.Techs
.filter(tech => tech.Address != "" && !tech.Notes.includes('Not'))
.map(tech => {
let techObj = {
TechName: tech.FirstName + " " + tech.LastName,
TechAddress: tech.Address + " " + tech.City + " " + tech.State + " " + tech.Zip,
KioskID: info.ID.toUpperCase(),
DriveTime: "",
};
return techObj
});
let temp = [...techs]; // create copy of techs array
const directionsService = new google.maps.DirectionsService();
recursion();
let count = 0;
function recursion() {
const techAddress = temp.shift(); // saves first element and removes it from array
directionsService.route({
origin: techAddress.TechAddress,
destination: destAddress,
travelMode: 'DRIVING'
}, function (res, status) {
if (status == 'OK') {
let time = res.routes[0].legs[0].duration.text;
techs[count].DriveTime = time;
} else {
console.log(status);
}
if (temp.length) { // if length of array still exists
count++;
setTimeout(recursion, 1000);
} else {
console.log('DONE');
}
});
}
return techs;
}
After this method is complete, it will return an array with the techs and their respective drive times to that destination. The problem here is that using setTimeout obviously doesn't stop execution of the rest of my code, so returning the array of technicians will just return the array with empty drive times.
After timeout is complete I want it to return the array within the method it was called like this:
function OtherMethod() {
// there is code above this to generate info and destAddress
let arr = GetTechDriveTimes(info, destAddress);
// other code to be executed after GetTechDriveTimes()
}
I've looked online for something like this, and it looks like I would need to use a Promise to accomplish this, but the difference from what I found online is that they weren't using it inside of a recursive method. If anyone has any ideas, that would help me a lot. Thanks!
You could use promises, but you can also create a callback with the "other code to be executed after GetTechDriveTimes" and send it to the function:
function OtherMethod() {
// there is code above this to generate info and destAddress
// instead of arr = GetTechDriveTimes, let arr be the parameter of the callback
GetTechDriveTimes(info, destAddress, function(arr) {
// other code to be executed after GetTechDriveTimes()
});
}
function GetTechDriveTimes(info, destAddress, callback) {
...
if (temp.length) { // if length of array still exists
...
} else {
console.log('DONE');
callback(techs); // send the result as the parameter
}
...
I cannot read the title attribute from the received variable, but as you see the collection is not undefined, and the title attribute also not undefined.
The error saying its undefined.
var received = document.getElementsByClassName("_2her");
if (received[0].title == "Delivered") {
chColorsForDelivered();
}
Your script started before DOM ready, If you are using html5, then use async with you script.
If you only want to check that first element (and make sure it's there after the page loads), I usually use this method:
window.onload = function() {
var received = document.getElementsByClassName("_2her")[0];
if (received.title == "Delivered") {
chColorsForDelivered();
}
}
You could also use querySelector to get the first occurrence of the class, which might be more suited to your needs:
var received = document.querySelector("_2her");
if (received.title == "Delivered") {
chColorsForDelivered();
}
var active;
var bg;
var received;
var rightDelivered;
var colorchint;
window.onload = function() {
main();
}
//=================== FUNCTIONS ===================
async function main() {
active = await document.getElementsByClassName("_2v6o");
bg = document.getElementsByClassName("_673w");
received = await document.getElementsByClassName("_2her");
rightDelivered = document.getElementsByClassName("_2jnt");
colorchint;
bg[0].onmouseover = function() {clearInterv();}
rightDelivered[0].onclick = function() {clearDeliv();}
//await sleep(2000);
if (active[0].innerText == "Active on Messenger") {
chColorsForActive();
}
else if (received[0].title == "Delivered") {
await chColorsForDelivered();
}
}
//for delivered
async function chColorsForDelivered() {
y = 1;
for (var i = 0; i < 6; i++) {
chColorsDelivered();
await sleep(1000);
}
}
function chColorsDelivered() {
if (y === 1) {
color = "#1e1e1e";
y = 2;
} else {
color = "orange";
y = 1;
}
rightDelivered[0].style.background = color;
}
//accessories
function sleep(ms) {
return new Promise(resolve => setTimeout(resolve, ms));
}
I put it into async. First I tried to
await for the variable received = await document.getElementsByClassName("_2her"); (it wasn't enough)
then I tried to sleep for 2 seconds (it was working)
tried to await the inner function (it was working too) But i have no idea why is it working if I wait for the inner function. I doesn't make sense to me. If I delete the await chColorsForDelivered(); await here It's complaining about the title is undefiend.
If you run this script in your messenger you will see an orange blinking span in the conversation information (where you see the pictures and stuffs, just delete the if)(It's only blinking if you have a Delivered message).
I'm currently struggling with a function call, when I call the function from an if statement it does work but when I call it from outside it doesn't, my if statement only checks which button was pressed but I'm trying to remove the function from the button and just call it as soon as my app starts.
We will look at fetchJokes() inside jokeContainer.addEventListener('click', event => {
This is my current code:
const jokeContainer = document.querySelector('.joke-container');
const jokesArray = JSON.parse(localStorage.getItem("jokesData"));
// Fetch joke count from API endpoint
async function sizeJokesArray() {
let url = 'https://api.icndb.com/jokes/count';
let data = await (await fetch(url)).json();
data = data.value;
return data;
}
// use API endpoint to fetch the jokes and store it in an array
async function fetchJokes() {
let url = `https://api.icndb.com/jokes/random/${length}`;
let jokesData = [];
let data = await (await fetch(url)).json();
data = data.value;
for (jokePosition in data) {
jokesData.push(data[jokePosition].joke);
}
return localStorage.setItem("jokesData", JSON.stringify(jokesData));;
}
const jokeDispenser = (function() {
let counter = 0; //start counter at position 0 of jokes array
function _change(position) {
counter += position;
}
return {
nextJoke: function() {
_change(1);
counter %= jokesArray.length; // start from 0 if we get to the end of the array
return jokesArray[counter];
},
prevJoke: function() {
if (counter === 0) {
counter = jokesArray.length; // place our counter at the end of the array
}
_change(-1);
return jokesArray[counter];
}
};
})();
// pass selected joke to print on html element
function printJoke(joke) {
document.querySelector('.joke-text p').textContent = joke;
}
sizeJokesArray().then(size => (length = size)); // Size of array in response
jokeContainer.addEventListener('click', event => {
if (event.target.value === 'Fetch') {
fetchJokes(length);
} else if (event.target.value === 'Next') {
printJoke(jokeDispenser.prevJoke(jokesArray));
} else if (event.target.value === 'Prev') {
printJoke(jokeDispenser.nextJoke(jokesArray));
}
});
And I'm trying to do something like this:
// pass selected joke to print on HTML element
function printJoke(joke) {
document.querySelector('.joke-text p').textContent = joke;
}
sizeJokesArray().then(size => (length = size)); // Size of array in response
fetchJokes(length);
jokeContainer.addEventListener('click', event => {
if (event.target.value === 'Next') {
printJoke(jokeDispenser.prevJoke(jokesArray));
} else if (event.target.value === 'Prev') {
printJoke(jokeDispenser.nextJoke(jokesArray));
}
});
By the way, I'm aware that currently, you can't actually iterate through the array elements using prev and next button without refreshing the page but I guess that will be another question.
Couldn't think of a better title.(edits welcomed)
Async functions are, as the name implies, asynchronous. In
sizeJokesArray().then(size => (length = size)); // Size of array in response
fetchJokes(length);
you are calling fetchJokes before length = size is executed because, as you may have guessed, sizeJokesArray is asynchronous.
But since you are already using promises the fix is straightforward:
sizeJokesArray().then(fetchJokes);
If you have not fully understood yet how promises work, maybe https://developers.google.com/web/fundamentals/getting-started/primers/promises helps.
I use the recursive function below, in order to reopen website if httpstatus != 200:
retryOpen = function(){
this.thenOpen("http://www.mywebsite.com", function(response){
utils.dump(response.status);
var httpstatus = response.status;
if(httpstatus != 200){
this.echo("FAILED GET WEBSITE, RETRY");
this.then(retryOpen);
} else{
var thisnow = hello[variable];
this.evaluate(function(valueOptionSelect){
$('select#the_id').val(valueOptionSelect);
$('select#the_id').trigger('change');
},thisnow);
}
});
}
The problem is that sometimes the retryOpen function does not even go as far as to callback function(response){}. Then, my script freezes.
I wonder how one could change the function to be able to recursively try to open website again if there is no response from website (not even some error code as 404 or something)? In other words, how to rewrite the retryOpen function so it reruns when the function does not reach callback after a certain amount of time?
I would try something like this. Please note this is untested code, but should get you on the correct path
retryOpen = function(maxretry){
var count = 0;
function makeCall(url)
{
this.thenOpen(url, function(response){
utils.dump(response.status);
});
}
function openIt(){
makeCall.call(this,"http://www.mywebsite.com");
this.waitFor(function check() {
var res = this.status(false);
return res.currentHTTPStatus === 200;
}, function then() {
var thisnow = hello[variable];
this.evaluate(function(valueOptionSelect){
$('select#the_id').val(valueOptionSelect);
$('select#the_id').trigger('change');
},thisnow);
}, function timeout() { // step to execute if check has failed
if(count < maxretry)
{
openIt.call(this);
}
count++
},
1000 //wait 1 sec
);
}
openIt();
}