Remove JSON Objects With Date Already Passed - javascript

I am trying to sort Shopify Blog Posts by a metafield called "Event Date." I call on my articles.JSON but it does not come with the metafields.JSON so I then have to take that array and put it through a foreach function to attach the metafields to each article.
This is how the metafields.json for each article is setup:
{
"metafields":[
{
"id":5994805788772,
"namespace":"global",
"key":"Event-Date",
"value":"1549256400",
"value_type":"string",
"description":null,
"owner_id":15977611364,
"created_at":"2019-02-06T18:31:44-05:00",
"updated_at":"2019-02-06T18:31:44-05:00",
"owner_resource":"article"
},
{
"id":5994805821540,
"namespace":"global",
"key":"Event-Time",
"value":"6:00pm - 8:00pm",
"value_type":"string",
"description":null,
"owner_id":15977611364,
"created_at":"2019-02-06T18:31:45-05:00",
"updated_at":"2019-02-06T18:31:45-05:00",
"owner_resource":"article"
},
{
"id":6010564542564,
"namespace":"global",
"key":"Location",
"value":"18th Street Location",
"value_type":"string",
"description":null,
"owner_id":15977611364,
"created_at":"2019-02-07T13:16:05-05:00",
"updated_at":"2019-02-07T14:05:08-05:00",
"owner_resource":"article"
}
]
}
How I attach the metafields.JSON below:
var request = new XMLHttpRequest();
request.open('GET', '/admin/blogs/43130421348/articles.json');
request.responseType = 'json';
request.send();
request.onload = function() {
var articleList = request.response;
var articleArray = articleList.articles;
var date = new Date();
var ticks = Math.floor(date.getTime() / 1000);
var count = 0;
articleArray.forEach(function(entry,index, object){
var metaRequest = new XMLHttpRequest();
metaRequest.open('GET', '/admin/blogs/43130421348/articles/'+ entry.id + '/metafields.json');
metaRequest.responseType = 'json';
metaRequest.send();
console.log(index);
metaRequest.onload = function() {
var articleMetaObj = metaRequest.response;
var articleMetaArr = articleMetaObj.metafields;
entry.metafields = articleMetaArr;
var eventDate = entry.metafields[0].value;
}
});
};
I'm now trying to get rid of any article that has a date ("Key": "Event-Date") that has already passed compared to the current date. I've looked at the following Stack Overflow Post on removing objects in a foreach loop but none of its solutions prove to actually get rid of all the articles. It will get rid all of them occasionally but sometimes leave in one of the objects.
I've also tried an array filter but all I've gotten back is an empty array when I've used it. I've been stuck on this for a bit now so any help on solving it is much appreciated.

I think it would be easiest if you waited until you attached all of the metadata, and then once it is all completed, use articleArray.filter to take out the ones you don't want. To do this, you have two options:
Option 1 - The Old Ways (setInterval)
Here, we keep count as the metadata is retrieved, and create an interval to check when they all have completed. Once done, a function is called (finish) that allows for continued processing.
var request = new XMLHttpRequest();
request.open('GET', '/admin/blogs/43130421348/articles.json');
request.responseType = 'json';
request.send();
request.onload = function () {
var articleList = request.response;
var articleArray = articleList.articles;
var date = new Date();
var ticks = Math.floor(date.getTime() / 1000);
var count = 0; //to keep track of how many metafields have been retrieved
var checkInterval = null;
articleArray.forEach(function (entry, index) {
var metaRequest = new XMLHttpRequest();
metaRequest.open('GET', '/admin/blogs/43130421348/articles/' + entry.id + '/metafields.json');
metaRequest.responseType = 'json';
metaRequest.send();
console.log(index);
metaRequest.onload = function () {
var articleMetaObj = metaRequest.response;
var articleMetaArr = articleMetaObj.metafields;
entry.metafields = articleMetaArr;
count++;
};
});
//Function to continue processing
var finish = function () {
articleArray = articleArray.filter(a => new Date(a.metafields[0].value).getTime() < date.getTime());
//Continue on...
};
//Wait until all metafields are retrieved to continue
checkInterval = setInterval(function () {
if (count === articleArray.length - 1) {
clearInterval(checkInterval);
finish();
}
}, 500);
};
Option 2 - The New Razmatazz (Promises & async/await)
Promises and async/await allow for writing some much nicer looking code when dealing with asynchronous operations.
If you feel like using these, I would suggest digging into the documentation to get more familiar, but here is what it could look like for your task.
//Functions that return Promises can be awaited...
var get = url => new Promise((resolve, reject) => {
var request = new XMLHttpRequest();
request.open('GET', url);
request.responseType = 'json';
//resolve is called when successful
request.onload = () => resolve(request.response);
//reject is called when there's a problem
request.onerror = err => reject(err);
request.send();
});
//await keyword must appear in an async function
var getArticles = async () => {
var articleList = await get('/admin/blogs/43130421348/articles.json');
return articleList.articles;
};
//Promise.all takes an array of promises and resolves when all of them are finished
//This lets you skip the messy setInterval stuff
var getArticleMetafields = async articles => {
var requests = [];
articles.forEach(a => {
var url = '/admin/blogs/43130421348/articles/' + a.id + '/metafields.json';
var promise = get(url);
requests.push(promise);
});
var responses = await Promise.all(requests);
responses.forEach((response, i) => {
articles[i].metafields = response.metafields;
});
return articles;
};
//Notice the async on the handler
document.addEventListener('DOMContentLoaded', async () => {
var articles = await getArticles();
articles = await getArticleMetafields(articles);
var date = new Date();
articles = articles.filter(a => new Date(a.metafields[0].value) < date);
//Continue...
});
Hope this helps. Cheers!

Is your entry the article? Then you could ignore using:
request.onload = function() {
var articleList = request.response;
var articleArray = articleList.articles;
var date = new Date();
var ticks = Math.floor(date.getTime() / 1000);
var count = 0;
articleArray.forEach(function(entry,index, object){
if(entry.'Key' !== 'Event-Date'){
var metaRequest = new XMLHttpRequest();
metaRequest.open('GET', '/admin/blogs/43130421348/articles/'+ entry.id + '/metafields.json');
metaRequest.responseType = 'json';
metaRequest.send();
console.log(index);
metaRequest.onload = function() {
var articleMetaObj = metaRequest.response;
var articleMetaArr = articleMetaObj.metafields;
entry.metafields = articleMetaArr;
var eventDate = entry.metafields[0].value;
}
}
});

Related

If/else statement in case JSON endpoint doesn’t exists

I am trying to create a scenario where if endpoint doesn’t exists, function still returns a value.
When the endpoint exists, I get a value, no problem.
var main = function(){
json_url = "http://ergast.com/api/f1/current/next.json";
xhr = new XMLHttpRequest();
xhr.open("GET", json_url, false);
xhr.send(null);
weather = JSON.parse(xhr.responseText);
let race = " ";
if (weather.MRData.RaceTable.Races[0].Sprint.date === undefined) {
race = " ";
}
else {
race = weather.MRData.RaceTable.Races[0].Sprint.date;
}
return race;
}
I get the following result
2022-07-09
But in case the endpoint doesn’t exists, I get a error.
var main = function(){
json_url = "http://ergast.com/api/f1/current/last.json";
xhr = new XMLHttpRequest();
xhr.open("GET", json_url, false);
xhr.send(null);
weather = JSON.parse(xhr.responseText);
let race = " ";
if (weather.MRData.RaceTable.Races[0].Sprint.date === undefined) {
race = " ";
}
else {
race = weather.MRData.RaceTable.Races[0].Sprint.date;
}
return race;
}
How can I rectify this. Do I have to assign something other than undefined in this case.
Use as below:
var main = function(){
json_url = "http://ergast.com/api/f1/current/last.json";
xhr = new XMLHttpRequest();
xhr.open("GET", json_url, false);
xhr.send(null);
weather = JSON.parse(xhr.responseText);
return weather.MRData.RaceTable.Races[0].date;
}
or:
race = weather.MRData.RaceTable.Races[0].FirstPractice.date;
or:
race = weather.MRData.RaceTable.Races[0].Qualifying.date;

How do I make 100 calls in the same function, to iterate over api endpoints? [Pokeapi]

I need to iterate over 151 pokemon, but the api endpoint looks like this https://pokeapi.co/api/v2/pokemon/1 where 1 is the first pokemon, and i need to iterate through to 151, calling a different endpoint every time. Is there a better way to do this? This is the code I have so far, and it doesn't work.
let pokeObj = {};
function pokeList() {
const url ='https://pokeapi.co/api/v2/pokemon/'
const xhr = new XMLHttpRequest();
xhr.onload = function() {
if (xhr.status === 200) {
for(let i = 1; i < 100; i++) {
pokeObj += JSON.parse(xhr.responseText);
xhr.open('GET', `${url + i.toString()}`, true);
xhr.send();
}
}
}
}
Try something like this:
(async () => {
let pokeObj = {};
const url ='https://pokeapi.co/api/v2/pokemon'
const xhr = new XMLHttpRequest();
for(let i = 1; i < 100; i++) {
const response = await new Promise(resolve => {
xhr.onload = function(e) {
resolve(e.target.response);
}
xhr.open('GET', `${url}/${i}`, true);
xhr.send();
});
console.log(response)
}
})();
Each request will be awaited before firing the next one, to be nice to the api server and not executing too many request in parallel.

return a response from an async call in a standard for loop

After reading How do I return the response from an asynchronous call? by Felix Kling, I am still confused about how I can return a value from an asynchronous callback.
My goal: convert a static image to base64 once and store that image in indexDB until indexDB throws some kind of storage error.
I am using this async idb npm module
// init the idb store
const initIDB = async () => {
const db = await openDB('db', 1, {
upgrade(db) {
db.createObjectStore('tempStore', { keyPath: 'id', autoIncrement: true });
},
});
const tx = db.transaction('tempStore', 'readwrite');
await overloadIDB(tx.store);
await tx.done;
return true;
};
// random number generator
const getRandomArbitrary = (min, max) => Math.random() * (max - min) + min;
// function will overload the idb
const overloadIDB = async (store) => {
const imgurl = "someLocalImage.png";
const promises = [];
return toDataURL(imgurl, async (s) => {
for (let i = 0; i < 10; i++) {
if (i > 0 && i % 100 === 0) console.log('A set done');
try {
const num = Math.round(getRandomArbitrary(1, 1000000));
const data = {
id: num,
img: s,
};
store.add(data);
} catch (e) {
console.log(e.toString());
console.dir(e);
break;
}
}
console.log('Done');
});
};
// convert image to base64
const toDataURL = (url, callback) => {
const xhr = new XMLHttpRequest();
xhr.onload = () => {
const reader = new FileReader();
reader.onloadend = () => {
callback(reader.result);
};
reader.readAsDataURL(xhr.response);
};
xhr.open('GET', url);
xhr.responseType = 'blob';
xhr.send();
};
Ideally, I would like to return the value from the toDataURL's callback function and use that result in the for loop but I always get undefined which makes sense due to asynchronous behaviour.
The above code fails to execute the transaction store.add(data) multiple times and fails when i = 0.
I have tried wrapping toDataURL with a new Promise(resolve, reject) like so
const toDataURL = (url, callback) => new Promise((resolve, reject) => {
const xhr = new XMLHttpRequest();
xhr.open('GET', url);
xhr.responseType = 'blob';
xhr.onload = () => {
const reader = new FileReader();
reader.onloadend = () => {
resolve(callback(reader.result));
};
reader.readAsDataURL(xhr.response);
};
xhr.send();
});
and then using Promise.all to resolve an array of stores like so
const overloadIDB = async (store) => {
const imgurl = 'someLocalImage.png';
const promises = [];
return toDataURL(imgurl, async (s) => {
console.log('s :', s);
for (let i = 0; i < 10; i++) {
if (i > 0 && i % 100 === 0) console.log('A set done');
try {
const num = Math.round(getRandomArbitrary(1, 1000000));
const data = {
id: num,
img: s,
};
promises.push(store.add(data));
} catch (e) {
console.log(e.toString());
console.dir(e);
break;
}
}
await Promise.all(promises);
console.log('Done');
});
};
but returns an error Failed to execute 'add' on 'IDBObjectStore': The transaction has finished.
At this point I think I my approach is flawed but I am not sure how I can fix it. Can anyone point to some solution please?
You cannot perform async operations in the middle of indexedDB operations. Perform your fetch entirely, then connect, create a transaction, and store the result.

How can I Make Use of Only One Function with My Weather App

In my weather app, I need to get the user location, which I'm getting from ipinfo.io, so that's one http request, and then I make another http request to another api on openweathermap.org. My question is how can I improve my code. Is it possible to make only one http request function and use it for calling both api by passing different parameters. Notice that I do set a number of variables inside each function which are particular to that function. I don't think it is possible to use these variables outside the scope of the function.
Here's my index.js
/*
Weather App Javascript code
author: George Louis
date: 3/11/2018
purpose: get local weather
*/
window.onload = function() {
//variables
var ipUrl = "https://ipinfo.io/json";
var appid = "appid=8e1880f460a20463565be25bc573bdc6";
var location = document.getElementById("location");
var currentDate = new Date();
var dayNight = "day";
//setting the date
var dateElem = document.getElementById("date");
var strDate = currentDate.toString();
dateElem.innerHTML = strDate.substring(0, strDate.length-18)
//calling ipinfo.io/json function
httpReqIpAsync(ipUrl);
//request to ipinfo.io/json
function httpReqIpAsync(url, callback) {
var httpReqIp = new XMLHttpRequest();
httpReqIp.open("GET", url, true)
httpReqIp.onreadystatechange = function() {
if(httpReqIp.readyState == 4 && httpReqIp.status == 200) {
var jsonIp = JSON.parse(httpReqIp.responseText)
var ip = jsonIp.ip;
var city = jsonIp.city;
var country = jsonIp.country;
location.innerHTML = `${city}, ${country}`;
var lat = jsonIp.loc.split(",")[0];
var lon = jsonIp.loc.split(",")[1];
console.log(lat+" "+lon)
var weatherApi = `http://api.openweathermap.org/data/2.5/weather?lat=${lat}&lon=${lon}&${appid}`;
//calling openweathermap api function
httpReqWeatherAsync(weatherApi);
}
}
httpReqIp.send();
}
//request to openweathermap.com json
function httpReqWeatherAsync(url, callback) {
var httpReqWeather = new XMLHttpRequest();
httpReqWeather.open("GET", url, true);
httpReqWeather.onreadystatechange = function() {
if(httpReqWeather.readyState == 4 && httpReqWeather.status == 200) {
var jsonWeather = JSON.parse(httpReqWeather.responseText);
console.log(jsonWeather)
var weatherDesc = jsonWeather.weather[0].description;
var id = jsonWeather.weather[0].id;
var icon = `<i class="wi wi-owm-${id}"></i>`
var temperature = jsonWeather.main.temp;
var tempFaren = Math.round(1.8 * (temperature - 273) + 32)
// console.log(tempFaren)
var humidity = jsonWeather.main.humidity;
var windSpeed = jsonWeather.wind.speed;
//converting visibility to miles
var visibility = Math.round(jsonWeather.visibility / 1000);
// console.log(visibility)
//find whether is day or night
var sunSet = jsonWeather.sys.sunset;
//sunset is 10 digits and currentDate 13 so div by 1000
var timeNow = Math.round(currentDate / 1000);
console.log(timeNow + "<" + sunSet +" = "+(timeNow < sunSet))
dayNight = (timeNow < sunSet) ? "day" : "night";
//insert into html page
var description = document.getElementById("description");
description.innerHTML = `<i id="icon-desc" class="wi wi-owm-${dayNight}-${id}"></i><p>${weatherDesc}</p>`;
var tempElement = document.getElementById("temperature");
tempElement.innerHTML = `${tempFaren}<i id="icon-thermometer" class="wi wi-thermometer"></i>` ;
var humidityElem = document.getElementById("humidity");
humidityElem.innerHTML = `${humidity}%`;
var windElem = document.getElementById("wind");
windElem.innerHTML = `${windSpeed}m/h`;
var visibilityElem = document.getElementById("visibility");
visibilityElem.innerHTML = `${visibility} miles`;
}
}
httpReqWeather.send();
}
}
You can use the modern way to request with fetch instead, if you like. You can also take advantage of destructuring. This is what I would do:
function httpReqIpAsync(url, callback) {
fetch(url)
.then(response => response.json())
.then(jsonIp => {
const { ip, city, country } = jsonIp;
location.textContent = `${city}, ${country}`;
const [lat, lon] = jsonIp.loc.split(",");
const weatherApi = `http://api.openweathermap.org/data/2.5/weather?lat=${lat}&lon=${lon}&${appid}`;
return [weatherApi, callback];
})
.then(httpReqWeatherAsync)
}
//request to openweathermap.com json
function httpReqWeatherAsync([url, callback]) {
// ...
Since each request is separate, I think it makes much more sense for them to be in separate functions, rather than consolidating them together.

How to acces data from variable in begin function

Could someone help me out on this function structure what I try to do is seperate the functions like this:
function getNewResources (currentUrl, nextUrl)
{
let resourcesCurrentUrl = getResourceByUrl(currentUrl);
let resourcesNextUrl = getResourceByUrl(nextUrl);
// Diff resources
ACCESS DATA FROM ABOVE VARIABLES HERE!
// Return new resources
}
function getResourceByUrl (url)
{
let xmlhttp = new XMLHttpRequest();
let xmlResponse;
xmlhttp.onreadystatechange = function() {
if (this.readyState == 4) {
// when succesfull
var resources = extractResourcesFromXMLResponse(this.response);
return resources;
}
};
xmlhttp.open("GET", url, true);
xmlhttp.send();
}
function extractResourcesFromXMLResponse (xmlResponse)
{
let resources = [];
// Add images
imagePaths = extractImages(xmlResponse);
resources.push(imagePaths);
return resources;
}
function extractImages(xmlDoc)
{
let match,
extractedImages = [],
newArr = [],
rex = /<img.*?src="([^">]*\/([^">]*?))".*?>/g;
while ( match = rex.exec( xmlDoc ) ) {
extractedImages.push( match[1] );
}
return extractedImages;
}
How do I acces the returned resource variable in the getNewResource() function

Categories

Resources