How to get data with axios from all api pages? - javascript

What am I doing wrong? I want to get data from all pages in api. After adding the while it stopped working, but I don't know how to start the page loop differently!
getCustomers: function() {
let url = '/crm/customer/';
return axios.get(url).then((response) => {
this.customers = response.data.results;
if (this.customers.length === 100) {
let i = 2;
axios.get('/crm/customer/?page=' + i).then((response) => {
this.c = response.data.results;
i += 1;
for (let item of this.c.values()) {
this.customers.push(item);
}
while (this.c.length === 100) {
axios.get('/crm/customer/?page=' + i).then((response) => {
this.c = response.data.results;
i += 1;
for (let item of this.c.values()) {
this.customers.push(item);
}
}).catch( error => {}).finally(() => (global_waiting_stop()));
}
}).catch( error => {}).finally(() => (global_waiting_stop()));
}
}).catch( error => {}).finally(() => (global_waiting_stop()));
},

What I would do is, first, use an async function called getPageOfResults:
async function getPageOfResults(page) {
const response = await axios.get('/crm/customer/?page=' + page);
return response.data.results; // .results.values()? I don't know
}
Then, inside another async function, you can have a loop that does what you want:
async function getAllResults() {
const customers = [];
let lastResultsLength = 100;
let page = 1;
while (lastResultsLength === 100) {
const newResults = await getPageOfResults(page);
page++;
lastResultsLength = newResults.length;
customers = customers.concat(newResults);
}
return customers;
}
So you've got a variable keeping track of what page your on, and you keep on getting new pages until you get a page with less than 100 results.
You're adding all the results together with the concat function, and returning the entire list at the end.

Related

How to write fetch synchronously?

I'm new in javascript/typescript I write blackjack app using typescript and in one player game I have bot as second player.
His script looks like this:
const botTurn = (bot : Player) =>{
while(bot.Points < 21)
{
if(bot.Points < 18)
{
hitMe();
}
else
{
var random = Math.random();
if(random < 0.5)
{
hitMe();
}
else{
break;
}
}
}
stay();
}
and hitMe looks like this:
const hitMe = () =>
{
fetch('https://deckofcardsapi.com/api/deck/' + deckId + '/draw/?count=1')
.then(response => response.json())
.then(data => {
deckLenght = data.remaining;
for(let card of data.cards)
{
var newCard = getCardData(card);
players[currentPlayer].Hand.push(newCard);
renderCard(newCard, currentPlayer);
updatePoints();
updateDeckLenght();
check();
}
});
}
So botTurn doesn't wait for hitMe to finish and my browser hangs
How to fix that?
you can use Async/await it will be better for your case and easier
const hitMe = async () =>
{
let response = await fetch('https://deckofcardsapi.com/api/deck/' + deckId + '/draw/?count=1');
let data = await response.json();
deckLenght = data.remaining;
for(let card of data.cards)
{
var newCard = getCardData(card);
players[currentPlayer].Hand.push(newCard);
renderCard(newCard, currentPlayer);
updatePoints();
updateDeckLenght();
check();
}
});
}
you cannot expect a synchronous-based scoped value by wrapping an asynchronous operation with a synchronous operation.
although you can try to finalize your logic inside resolver, ie: then(result)

How can I auto increment a value whenever a member is mentioned in a message

client.on('message', async function(message) {
if(message.author.bot) return;
if(message.content.includes('!reps')) {
let usname = message.mentions.users.first().username;
let usId = message.mentions.users.first().id;
message.channel.send('Calculating ' + usname + `'s reps`);
let repsChannel = client.channels.cache.get('66518878191720806');
let sbChannel = client.channels.cache.get('66503449499402200');
let chData = await repsChannel.messages.fetch( {limit:100}).catch(err => console.log(err));
let mentamt = chData.filter(chData => chData.mentions.members.has(usId))
let amts = mentamt.size
console.log(amts)
This portion gives me the first 100 lines to check through and it seems to work, however, after including the While loop to check through the next 100 lines, I cant make it return the total sum
while(chData.size === 100) {
let lastKeyId = chData.lastKey();
chData = await repsChannel.messages.fetch( {limit:100, before: lastKeyId }).catch(err => console.log(err));
let tvalue = chData.filter(chData => chData.mentions.members.has(usId)
let tamt = 0
}
sbChannel.send('> '+ usname +' has '+ amts);
}
});
I want to auto increment the value of tamt whenever a member has been mentioned in a message.
The main purpose of this is to calculate the total number of times a member has been mentioned.
Using async-await in for loop, you can achieve it easily.
const delay = () =>
new Promise((r) => {
setTimeout(r, 300, new Date());
});
(async function () {
async function page(count) {
const result = await delay();
// Do your work here
return `Page ${count}:: ${result}`;
}
const chunkSize = 100;
const pageCount = Math.floor(1121/chunkSize) + 1
for (let pageNum = 0; pageNum < pageCount; pageNum++) {
const data = await page(pageNum);
console.log(data)
}
})();

Firestore Cloud Function Query returning value after database query?

Currently, I am querying for objects in the database to update. A snippet of the function that does so is
return getPostsForDate.get().then(snapshot => {
const updates = {}
var counter = 0
const batch = admin.firestore().batch()
snapshot.forEach((doc) => {
var key = doc.id
return admin.database().ref('/convoID/' + key).once('value', (snapshot) => {
if (snapshot.exists()) {
const convoIDCollection = snapshot.val()
for (var child in convoIDCollection) {
console.log(child)
updates["conversations/" + child] = null
updates["messages/"+ child] = null
updates["convoID/"+ child] = null
}
}
updates["/convoID/" + key] = null
updates["/reveals/" + key] = null
updates["/postDetails/" + key] = null
const postFireStoreRef = admin.firestore().collection('posts').doc(key)
const posterRef = admin.firestore().collection('posters').doc(key)
batch.delete(postFireStoreRef)
batch.delete(posterRef)
counter++
console.log(counter)
})
})
if (counter > 0) {
console.log("at the deletion point")
return Promise.all[admin.database().ref().update(updates), batch.commit()]
}
else {
console.log("null")
return null
}
})
Essentially, after the firestore queries for the posts, additional details are received from the realtime database and added to an array. Finally, I commit all these updates through a promise. However, the function that returns the updates is never reached - in order to ensure that updates are needed to the database, I have a counter that counts the number of updates. If it is greater than 0, I return
return Promise.all[admin.database().ref().update(updates), batch.commit()]
However, it seems like the return function is executed before the additional details are received, as it keeps on returning "null" to the console log which should only happen if the counter is less than 0.
Essentially, how do I wait for the data to be queried before executing the updates?
Update
return getPostsForDate.get().then(snapshot => {
const updates = {}
const promises = []
var counter = 0
const batch = admin.firestore().batch()
snapshot.forEach((doc) => {
promises.push (
admin.database().ref('/convoID/' + key).once('value', (snapshot) => {
if (snapshot.exists()) {
const convoIDCollection = snapshot.val()
for (var child in convoIDCollection) {
updates["conversations/" + child] = null
updates["messages/"+ child] = null
updates["convoID/"+ child] = null
}
}
updates["/convoID/" + key] = null
updates["/reveals/" + key] = null
updates["/postDetails/" + key] = null
const postFireStoreRef = admin.firestore().collection('posts').doc(key)
const posterRef = admin.firestore().collection('posters').doc(key)
batch.delete(postFireStoreRef)
batch.delete(posterRef)
counter++
})
)
})
Promise.all(promises).then(() => {
if (counter > 0) {
console.log("at the deletion")
return Promise.all[admin.database().ref().update(updates), batch.commit()]
}
else {
console.log("null")
return null
}
})
})
})
admin.database().ref('/convoID/' + key).once(...) is asynchronous and returns a promise, but your code isn't using those promises to wait for each query to complete. This means your code is moving on to the if/else immediately after the snapshot iteration, but before counter is ever updated, or the batches are added.
You'll need to restructure your code to collect all those promises from once() into an array, use Promise.all() to wait for all those promises to resolve, then commit the batch.

Chaining Promises Cross Function Javascript

I suspect I've fundementally misunderstood Javascript promises, any ideas?
I have a pretty function that queries a database containing music that looks like this:
function searchDatabaseForTrack(query,loadedResults){
loadedResults = loadedResults || [];
desiredResults = 100;
if (loadedResults.length < desiredResults) {
try {
databaseApi.searchTracks(query, {"offset":loadedResults.length, "limit":"50", }).then(function(data){
i=0
if (data.tracks.items.length == 0) {
console.log(`Already loaded all ${loadedResults.length} tracks!`)
console.log(loadedResults)
return loadedResults;
}
else {
for (thing in data.tracks.items){
loadedResults.push(data.tracks.items[i]);
i=i+1;
}
console.log(loadedResults.length, " tracks collected");
searchDatabaseForTrack(query,loadedResults)
}
});
} catch(err) {
console.log("ERROR!", err)
console.log(loadedResults)
return loadedResults;
}
} else {
console.log(loadedResults)
return loadedResults;
}
}
And then a bit later, I try to call and use the data retrieved.
function getArtistTracks(artistName){
searchDatabaseForTrack(artistName).then(function(data){
console.log(songs);
songs.sort(function(a,b){
var c = new Date(a.track.album.release_date);
var d = new Date(b.track.album.release_date);
return d-c;
});
console.log("songs", songs);
var newsongs=[];
i=0
for (song in songs) {
newsongs.push(songs[i].track.uri);
i++
};
return newsongs;
});
}
What I'm trying to do is get the second function "getArtistTracks" to wait for the completion of the query in the first function. Now I could just call the databaseApi.searchTracks directly, but there's a limit of 50 tracks returned per result — which kind of screws me over.
searchDatabaseForTrack().then(...) shouldn't work since searchDatabaseForTrack() doesn't return a promise, so you can either return a promise or use an async function.
instead of a recursive function, you could simply call databaseApi in a for loop,
the desiredResult should be an argument and not hardcoded in the function,
async function searchDatabaseForTrack(query, desiredResults){
let loadedResults = [], data, currOffset = 0;
const iterations = Math.ceil(desiredResults / 50);
for(let n = 0 ; n < iterations; n++){
cuurOffset = n * 50;
data = await databaseApi.searchTracks(query, {"offset":currOffset, "limit":"50", });
if (data.tracks.items.length == 0) {
console.log(`Already loaded all ${loadedResults.length} tracks!`)
console.log(loadedResults)
return loadedResults;
}
else {
loadedResults = loadedResults.concat(data.tracks.items);
console.log(loadedResults.length, " tracks collected");
}
}
return loadedResults;
}
the rest should be fine as long as you add .catch() to handle errors ( as mentionned in previous answer ) which are thrown automatically without the need of the try/catch block :
function getArtistTracks(artistName, 100){
searchDatabaseForTrack(artistName).then((songs) => {
// your previous code
})
.catch((err) => {
// handle error
});
});
Have searchDatabaseForTrack use Promise.all to return the loadedResults after all results have been gotten. Also, make sure not to implicitly create global variables as you're doing with thing. For example, try something like this:
async function searchDatabaseForTrack(query) {
const desiredResults = 100;
const trackPromises = Array.from(
({ length: Math.ceil(desiredResults / 50) }),
(_, i) => {
const offset = i * 50;
return databaseApi.searchTracks(query, { offset, limit: 50 });
}
);
const itemChunks = await Promise.all(trackPromises);
const loadedResults = itemChunks.reduce((a, { tracks: { items }}) => (
[...a, ...items]
), []);
return loadedResults;
};
and
searchDatabaseForTrack(artistName).then((loadedResults) => {
// do stuff with loadedResults
})
.catch((err) => {
console.log("ERROR!", err)
// handle error
});

How do I assign a variable defined in a synchronous scope in an async function?

I am trying to write a GUI frontend that uses a service to get data about the system. I am using a net.Socket for the client end of this. I want to be able to access certain variables assigned in the data event handlers in other modules but the assignment does not stay after that callback function finishes.
Problematic code:
client.on('data', (data) => {
var array = [...data];
array.splice(0,2);
for (var i=0;i<array.length;i++) {
dataInBuffer = dataInBuffer + String.fromCharCode(array[i]);
}
console.log(dataInBuffer);
if (dataInBuffer.startsWith('batStat')) {
let lastBatteryJSON = JSON.parse(dataInBuffer.split(';')[1]);
module.exports.hasBattery = lastBatteryJSON.hasBattery == 'true';
module.exports.isCharging = lastBatteryJSON.isCharging == 'true';
module.exports.lastBatteryReading = parseFloat(lastBatteryJSON.batteryLife);
}
dataInBuffer = '';
});
Those three exported variable assignments don't actually work, the variables always either stay undefined or their default values outside of the function. I tried using a Promise to solve this problem but got the same result. I'm at a loss and I can't find any other questions or forum posts that solve this problem.
EDIT
I do not have the option of moving the code that depends on those variables into the callback. In order to do that I would have to wait for the data every frame and flood the server as a result.
As apple commented; you can export an object and mutate it every time you receive data:
const data = {};
client.on('data', (data) => {
var array = [...data];
array.splice(0, 2);
for (var i = 0; i < array.length; i++) {
dataInBuffer = dataInBuffer + String.fromCharCode(array[i]);
}
console.log(dataInBuffer);
if (dataInBuffer.startsWith('batStat')) {
let lastBatteryJSON = JSON.parse(dataInBuffer.split(';')[1]);
//mutate the data object
data.hasBattery = lastBatteryJSON.hasBattery == 'true';
data.isCharging = lastBatteryJSON.isCharging == 'true';
data.lastBatteryReading = parseFloat(lastBatteryJSON.batteryLife);
}
dataInBuffer = '';
});
//export the data object
module.exports.batteryData = data;
Or as CertainPerformance answered you can have the caller decide when to ask for the information and provide a promise.
Here is an extended version of CertainPerformance answer that listens to error as well so a promise can be rejected and cleans up the event listeners when promise is resolved or rejected:
//wrapper for client.on to add and remove event listeners
const listeners = (function(){
var listenerCounter = -1;
const listeners = [];
const triggerEvent = event => data =>{
listeners.filter(
listener=>listener[2] === event
).forEach(
listener=>listener[1](data)
);
};
client.on('data', triggerEvent("data"));
client.on('error', triggerEvent("error"));//assuming you have an error event
return {
add:(event,fn)=>{
listenerCounter = listenerCounter + 1;
if(listenerCounter>1000000){
listenerCounter=0;
}
listeners.push([listenerCounter,fn,event]);
return listenerCounter;
},
remove:num=>{
listeners = listeners.filter(
listener=>{
num !== listener[0];
}
)
}
}
}());
//convert data to object or false
const getObjectFromData = data => {
var array = [...data];
var dataInBuffer="";
array.splice(0,2);
for (var i=0;i<array.length;i++) {
dataInBuffer = dataInBuffer + String.fromCharCode(array[i]);
}
console.log(dataInBuffer);
if (dataInBuffer.startsWith('batStat')) {
let lastBatteryJSON = JSON.parse(dataInBuffer.split(';')[1]);
return {
hasBattery : lastBatteryJSON.hasBattery == 'true',
isCharging : lastBatteryJSON.isCharging == 'true',
lastBatteryReading : parseFloat(lastBatteryJSON.batteryLife)
};
}
return false;
}
//export this function
const getBatteryData = () =>
new Promise((resolve,reject) => {
const removeListeners = ()=>{
listeners.remove(okId);
listeners.remove(errorId);
}
const okId = listeners.add(
"data",
data=>{
const resultObject = getObjectFromData(data);
if(resultObject){
resolve(data);
removeListeners();//clean up listeners
}else{
//not sure of on data is triggered multiple times by client.on.data
// if it is then at what point do we need to reject the returned promise?
}
}
)
const errorId = listeners.add(
"error",
error=>{
reject(error);
removeListeners();//clean up listeners
}
)
});
//you can call getBatteryData like so:
//getBatteryData()
// .then(batteryData=>console.log(batteryData))
// .catch(error=>console.warn("an error getting battery data:",error))
Your module should export a function that returns a promise that returns the desired values. Also, use const and not var when possible:
let resolveObj;
const haveData = new Promise((resolve) => {
let resolved = false;
client.on('data', (data) => {
const array = [...data];
array.splice(0, 2);
for (let i = 0; i < array.length; i++) {
dataInBuffer = dataInBuffer + String.fromCharCode(array[i]);
}
console.log(dataInBuffer);
if (dataInBuffer.startsWith('batStat')) {
const {
hasBattery,
isCharging,
batteryLife,
} = JSON.parse(dataInBuffer.split(';')[1]);
resolveObj = {
hasBattery: hasBattery === 'true',
isCharging: isCharging === 'true',
lastBatteryReading: Number(batteryLife),
};
if (!resolved) resolve();
resolved = true;
}
dataInBuffer = '';
});
});
const getData = () => haveData.then(() => resolveObj);
module.exports = getData;
Then consume with
moduleFunction().then(({ hasBattery, isCharging, lastBatteryReading }) => {
// do something with results
});
If called before resolveObj is populated, the promise will wait until the first client.on('data' to resolve. After that, the function will return a promise that resolves immediately to the current value of resolveObj (which will be properly updated on client.on('data')

Categories

Resources