Push data into array's specific index after asynchronous request from server - javascript

I've an object and I push that into main array just when document is loaded, now after response from server data is pushed into specific arrays which are nested into main array.
I have tried making a new instance of MainData each time when I want to push into array, but object name has to be same (as there are lot of requests and all of them has to be asynchronous) that is why it is mixing up values.
I am doing right now:
// Knockout viewModel
function ViewModel() {
self = this
self.main_array = ko.observableArray([])
}
var pointer = new ViewModel();
ko.applyBidings(pointer);
// Constructor for dummy data
function MainData() {
self = this
self.id = ko.observable()
self.first_val = ko.observableArray()
self.second_val = ko.observableArray()
}
var main_data;
// Main function which is called when document is loaded
num_type = (data) => { // data is 1234
main_data = new MainData();
main_data["id"] = data;
pointer.main_array.push(main_data);
// Fetch Requests
fetch("Num/Func1").then(x => {
x.json().then(b => {
main_data.first_val(b);
char_type(b[0].char); // this has to be called once b is loaded
})
})
// Another fetch request which gives a little late response
fetch("Num/Func2").then(x => {
x.json().then(b => {
// It takes 10 plus seconds meanwhile char_type function has fetched data
main_data.second_val(b);
})
})
}
// char_type function
char_type = (data) => { // data is abc
main_data = new MainData();
main_data["id"] = data;
pointer.main_array.push(main_data);
// Fetch Request
fetch("Char/Func1").then(x => {
x.json().then(b => {
main_data.first_val(b); // This response comes before fetch to 'Num/Func2'
})
})
fetch("Char/Func2").then(x => {
x.json().then(b => {
main_data.second_val(b); // This response also comes before fetch to
//'Num/Func2'
})
})
}
The values from both indices mixed together i.e index that is having main_data.id = 123 carries data from index which is having main_data.id = abc
Also There are some server calls which are outside num_type and char_type function.
How to get rid of that mixing stuff?

You need to move the declaration of main_edata inside the function, so you get a different, local copy each time the function is called. Then, the fetch callbacks will close over the local copy related to the num_type/char_type call that triggered that specific fetch.
Change this:
var main_data;
// Main function which is called when document is loaded
num_type = (data) => { // data is 1234
main_data = new MainData();
// ...
}
to this:
// Main function which is called when document is loaded
num_type = (data) => { // data is 1234
var main_data = new MainData(); // <====== Note `var`
// ...
}
And add var before main_data in char_type, too.

You need to create local variable for each main function and then pass that to other functions.
like:
num_type = (data) => {
var main_data = new MainData();
other_fun(data,main_data)
}

Related

Call Fetch Data from API once

Fetch is often used to retrieve data from api. But if you want to retrieve this data in every line of the code file, you have to type fetch again and again and iterate through these data. But if i want to call only part of it for example username. Look example:
fetch ('/ API')
         .then ((res) => res.json ())
         .then ((data) => {
         let output = 'Here is my output<br />';
         data.forEach (function (item)
         {
         output + = `$ {item.username}`;
         })
         document.getElementById ('here'). innerHTML = output;
         })
if i want to call $ {item.username} in each place in the file without calling fetch.
How to save these data in a variabel and call it every time i want.
Sorry for my bad English.
But if you want to retrieve this data in every line of the code file, you have to type fetch again and again and iterate through these data.
You can call fetch one time and keep the response in your local state, things like username usually stay the same so you should not call that endpoint again just to fetch a username because you can take it from your previous response.
It depends on your framework, but you can write window.username = usernameFromFetch on your first call, and you will have access to it every where (which is not the best aproach but it will work).
From what I understand through your question is that you want to access variable item.username globally and from the code it looks to be an array of users with username property.
In this case, you can define a variable object
var data = {
usernames = []
};
before your fetch function and the value will be initialized to an empty array and as soon as you fetch('/API') users you can do something like this
data.forEach (function (item)
{
data.usernames.push(item.username);
})
data.usernames will have your usernames throughout your current JS file.
access by
data.usernames[0]
data.usernames[1]
data.usernames[2] or
let output = 'Here is my output<br />';
data.usernames.forEach(function(username){
output + = `$ {username}`;
})
document.getElementById('here').innerHTML = output;`
You can use a memoization function to save the result:
// Stores in memory pairs of request urls (arguments) and its result
const memo = (callback) => {
const cache = new Map();
return (...args) => {
const selector = JSON.stringify(args);
if (cache.has(selector)) return cache.get(selector);
const value = callback(...args);
cache.set(selector, value);
return value;
};
};
// Memoize the fetch function. This function 'cachedRequest' should be used across your application
const cachedRequest = memo(fetch);
// Your url to call
const URL = "/API";
// First time the fetch is called, the result will be stored in the memory of 'memo' function. Second time its called, it will be retrieved from the previous in memory result and the fetch call wont be done
cachedRequest(URL).then((response) => {
console.log({response});
}).catch((error) => {
console.log({error});
})
Try this using localStorage:
fetch ('/ API')
.then ((res) => res.json ())
.then ((data) => {
// Store
localStorage.setItem("localData", data);
// Retrieve
var data = localStorage.getItem("localData");
let output = 'Here is my output<br />';
data.forEach (function (item)
{
output + = `$ {item.username}`;
})
document.getElementById ('here'). innerHTML = output;
})
Or use sessionStorage:
fetch ('/ API')
.then ((res) => res.json ())
.then ((data) => {
// Store
sessionStorage.setItem("localData", data);
// Retrieve
var data = sessionStorage.getItem("localData");
let output = 'Here is my output<br />';
data.forEach (function (item)
{
output + = `$ {item.username}`;
})
document.getElementById ('here'). innerHTML = output;
})

How to save firebase returned data to a variable

I am coding for my React Native app and I am having trouble getting the data from the firebase return outside of the firebase.firestore().collection("test_data").doc(ID) loop. Whenever I check the dataArray variable after the loop it is empty. If I check it within the loop, the data is there. I think that it is a scope problem, but I just do not understand it. I also cannot call any user defined functions inside of the loop.
try {
let dataArray = [];
// get the document using the user's uid
firebase.firestore().collection("users").doc(uid).get()
.then((userDoc) =>{
// if the document exists loop through the results
if (userDoc.exists) {
data = userDoc.data().saved_data; // an array store in firebase
data.forEach(ID => { // loop through array
firebase.firestore().collection("test_data").doc(ID).get()
.then((doc) => {
dataArray.push(doc.data().test_data);
console.log(dataArray) // the data shows
})
console.log(dataArray) // the data does not show
})
}
})
}
catch (error) {
}
}
You're looping through asynchronous calls, so your final console.log will trigger before the data has been received. Your first console.log only triggers after the data has been received.
So the code is working, but the function (promise) will resolve (as undefined, or void) before all the data has been received from your firebase calls.
If you want to return the array to the caller, you could do something like this:
function getDataFromServer() {
// get the document using the user's uid
return firebase.firestore().collection('users').doc(uid).get().then(userDoc => {
// now we're returning this promise
const dataArray = []; // can move this to lower scope
// if the document exists loop through the results
if (userDoc.exists) {
const savedData = userDoc.data().saved_data; // an array store in firebase
return Promise.all(
// wait for all the data to come in using Promise.all and map
savedData.map(ID => {
// this will create an array
return firebase.firestore().collection('test_data').doc(ID).get().then(doc => {
// each index in the array is a promise
dataArray.push(doc.data().test_data);
console.log(dataArray); // the data shows
});
})
).then(() => {
console.log(dataArray);
return dataArray; // now data array gets returned to the caller
});
}
return dataArray; // will always be the original empty array
});
}
Now the function returns the promise of an array, so you could do...
const dataArray = await getDataFromServer()
or
getDataArrayFromServer().then(dataArray => {...})

How to run JSON files and function on order

In my code, I want to load 2 JSON files first, then based on the result of the them, run another two JSON files, and then run a sequence of functions such as render, DOM etc. I want to save the JSON data in variable so I can refer to them later in the code.
Something like this:
$.when(parseJSON1(), parseJSON2())
.then(
parseJSON3(station_data.dj), parseJSON4(station_data.songurl)
)
.then(
_cacheOptions
)
.then(
_cacheDom
)
.then(`enter code here`
_events
).then(
_render
);
});
var station_data, history_data, itunes_data, coverList_data;
// Core Functions
function _cacheOptions() {
station_data = stationInfo[0];
history_data = stationHistory[0];
itunes_data = itunesInfo[0];
coverList_data = coverInfo[0];
}
function _cacheDom() {}
function _events() {}
function _render() {}
// Functions
function parseJSON1() {
return $.getJSON(settings.JSON1);
}
function parseJSON2() {
return $.getJSON(settings.JSON2);
}
function parseJSON3(searchTerm) {
return $.getJSON(settings.JSON3);
}
function parseJSON4() {
return $.getJSON(settings.JSON4);
}
So to make it simple, I want to run JSON1 and JSON2, then save its data as variables, then based on that data run JSON3 and JSON4 and save their variables. Then run the rest of the main functions.
The above would be the backbone of the plugin and I am trying to keep it very structural that everything runs on order.
Any idea how to make it work?
Answer:
You can use $.when in combination with $.getJSON like you have been, however it would be best to wrap this into a async function so that you don't have to worry about so many moving parts.
Create a store object for returned json.
Get the first two datasets
Put data in store
Check on your first two returned datasets
if the check passes continue the promise chain
Get the last two datasets with a $.when call
Put data in store
Return store
do something afterwards by using getAll().then(fn)
async function getAll() {
let json_store = {},
combine = (...locations) => locations.map($.getJSON),
json_check = (first, second) => (first.userId && second.userId);
await $.when(...combine(settings.JSON1, settings.JSON2)).then(function([first], [second]) {
json_store = Object.assign(json_store, {
first,
second
});
if (json_check(first, second)) {
return $.when(...combine(settings.JSON3, settings.JSON4)).then(function([third], [fourth]) {
json_store = Object.assign(json_store, {
third,
fourth
});
});
}
})
return json_store;
};
getAll().then(console.dir);
Example:
let settings = {
JSON1: "https://jsonplaceholder.typicode.com/todos/1",
JSON2: "https://jsonplaceholder.typicode.com/todos/2",
JSON3: "https://jsonplaceholder.typicode.com/todos/3",
JSON4: "https://jsonplaceholder.typicode.com/todos/4"
}
async function getAll() {
let json_store = {},
combine = (...locations) => locations.map($.getJSON),
json_check = (first, second) => (first.userId && second.userId);
await $.when(...combine(settings.JSON1, settings.JSON2)).then(function([first], [second]) {
json_store = Object.assign(json_store, {
first,
second
});
if (json_check(first, second)) {
return $.when(...combine(settings.JSON3, settings.JSON4)).then(function([third], [fourth]) {
json_store = Object.assign(json_store, {
third,
fourth
});
});
}
})
return json_store;
};
getAll().then(console.dir);
<script src="https://cdnjs.cloudflare.com/ajax/libs/jquery/2.2.4/jquery.min.js"></script>

How to make my function wait before it returns another call

I have a service that returns some translations. It makes a apex call which is async in nature and returns the translations. Every time I get a set of results I store the translations inside session storage and next time when a call comes I first check it inside the cached translations and if available then I return. The problem comes when the user sends multiple request for translations for the same set of values. For that I am keeping a track of the apex call already in progress. However I am not sure how to make my request wait for the previous apex call to end and return the results.
Here is the service code.
let labelQueue = [];
let labelMap;
let requestCustomLabels = function(labelsArr, language) {
return new Promise(function(resolve, reject) {
let labelsArray = [...labelsArr];
let returnedLabels = {};
let cachedLabels = JSON.parse(sessionStorage.getItem("customLabels")) || {};
labelMap = { ...cachedLabels };
if (Object.keys(labelMap).length > 0) {
labelsArray.forEach(item => {
if (Object.keys(labelMap).indexOf(item) > -1) {
labelsArray = labelsArray.filter(label => label !== item);
//push the available lables inside returnedLabels
returnedLabels[item] = labelMap[item];
}
});
}
if (Object.keys(returnedLabels).length === labelsArr.length) {
resolve(returnedLabels);
}
if (labelQueue.length && labelsArray.length) {
labelsArray.forEach(item => {
if (labelQueue.indexOf(item) > -1) {
labelsArray = labelsArray.filter(label => label !== item);
}
});
}
if (labelsArray.length) {
// store labels to the queue
labelsArray.forEach(label => {
labelQueue.push(label);
});
// then make call to apex for the result and store it inside returnedLabels & localmap and remove them from labelsQueue
fetchLabels(labelsArray, returnedLabels, language).then(
labelsReturned => {
returnedLabels = { ...returnedLabels, ...labelsReturned };
resolve(returnedLabels);
}
);
} else {
// wait for apex to return the call;
}
});
};
let fetchLabels = function(labelsArray, returnedLabels, language) {
return new Promise(function(resolve, reject) {
getUserProfile().then(function(userData) {
language = language || userData.language;
getCustomLabel(labelsArray, language)
.then(res => {
let labels = JSON.parse(res).data;
for (const key in labels) {
if (key !== "language" && key !== "totalSize") {
//when the apex call returns remove the labels from the label Queue.
labelQueue = labelQueue.filter(label => label !== key);
returnedLabels[key] = labels[key];
labelMap[key] = labels[key];
}
}
// store localsMap to the storage
sessionStorage.setItem("customLabels", JSON.stringify(labelMap));
resolve(returnedLabels);
})
.catch(function(error) {
console.error("error : ", error);
reject(error);
});
});
});
};
I actually scripted this, mostly for fetching language files that are called from multiple elements on the page in a shadow dom app.
You need to:
Check if the promise is stored.
Save the ajax promise as a variable.
Store the variable, if it's not stored.
Fetch the promise, or returned the stored promise.
Remove the promise.
var ajaxQueue = {};
/**
* #description Avoids unnecessary calls to server
* #param {String} url The api call
* #param {String} method 'GET', 'POST', 'PUT', 'DELETE'
* #param {String} data If sending data through POST/PUT
*/
function queueAJAXCalls(url, method, data) {
var property = FXAjaxBehavior.getQueueProperty(url, method);
if (firstTimeRequesting(property)) { // 1
var ajaxPromise = someGeneralCodeForFetchingAjaxData(url, method, data) // 2
.then(function(serverData) {
removeFromAJAXQueue(property); // 5
return serverData;
}).catch(function(error) {
removeFromAJAXQueue(property); // 5
console.warn(error);
return false;
});
storeInAJAXQueue(property, ajaxPromise); // 3
}
return getAJAXFromQueue(property); // 4
}
function getQueueProperty(url, method) {
return encodeURIComponent(method + url);
}
function firstTimeRequesting(property) {
return !ajaxQueue[property];
}
function removeFromAJAXQueue(property) {
delete ajaxQueue[property];
}
function storeInAJAXQueue(property, ajaxPromise) {
ajaxQueue[property] = ajaxPromise;
}
function getAJAXFromQueue(property) {
return ajaxQueue[property];
}
Example:
queueAJAXCalls('http://somedomain.com/api/fetchstuff', 'GET')
.then(function(serverResponse) {
// do stuff, like using JSON.parse(serverResponse)
})
Be aware the danger with parsing the jsondata in one place, namely in someGeneralCodeForFetchingAjaxData, and returning arrays and objects, because that means that changing the array/object in one place will change it in all places.
Instead of implementing this, I guess you could also use a Service Worker.

Injecting data object to window with Puppeteer

Background
I am using Puppeteer to create some PDFs. I need to inject some data into the page when Puppeteer loads it.
Problem
I have tried using evaluateOnNewDocument() which was successful when using a String only. When I try with an Object it fails. I also tried with evaluate() and it fails regardless of what I pass in.
Example
// Works
await page.evaluateOnNewDocument(() => {
window.pdfData = {};
window.pdfData = "Some String";
});
// Does not work
await page.evaluateOnNewDocument(() => {
window.pdfData = {};
window.pdfData = data;
});
// Fails
await page.evaluate(data => {
window.pdfData = {};
window.pdfData = data;
}, data);
I would like to access this object like this,
const data = window.pdfData;
Question
What is the proper way to pass a data object into window on a loaded Puppeteer page so that it can be accessed within the page to use the data client side?
Passing object to evaluate
You can pass data which will be serialized as JSON.
await page.evaluateOnNewDocument(data => { // <-- pass as parameter
window.pdfData = data; // <-- read it here
}, data); // <-- pass as argument
Passing object to evaluateOnNewDocument
evaluateOnNewDocument works similarly to evaluate, except it will run whenever there is a new window/navigation/frame. This way the data will stay even if you navigate away to another page.
You can pass data and read inside the function.
await page.evaluateOnNewDocument(data => {
window.pdfData = data;
}, data);

Categories

Resources