Updating outside value from inside a promise - javascript

I have a getValidUrls function that takes a maxId param.
Within this function it loops backwards and sends a request for the url.
Each loop decrements the maxId.
My issue is that I am trying to add these valid urls to an array, but I cannot update the array from within the .then of the promise. I have added a simple total variable to see if I could increment it and could not.
getValidUrls = (maxId) => {
return new Promise((resolve, reject) => {
let validUrls = [];
let idCounter = maxId;
let total = 0; // test to see if updated from inside (it doesn't)
// while(validUrls.length < 10 && idCounter > 0) {
for(let i = maxId; i > 0; i--){
let newsUrl = `https://hacker-news.firebaseio.com/v0/item/${i}.json?print=pretty`;
//console.log(newsUrl); // this shows all the urls & works
getJSONObject(newsUrl)
.then((story) => {
total++;
console.log(total); // this never gets shown
return getUserObject(story.by);
}).then((user) => {
if(user.karma > 5) {
validUrls.push(story);
if(validUrls.length >= 10) {
resolve(validUrls);
}
}
});
}
});
};
The following returns a json object for the url
getJSONObject = (url) => {
return new Promise((resolve, reject) => {
console.log(url); // this works and shows all urls
https.get(url, (response) => {
response.on('data', (data) => {
console.log(JSON.parse(data)); // This never gets shown
resolve(JSON.parse(data));
}, (err) => reject(err));
}, (err) => reject(err));
});
};

Related

JS Pagination Using Promises

I'm attempting to make an API call using promises. The API is paginated and as such, depending on the headers in that first API call make more to get the rest of the results if need be.
Here's what I have so far:
const get = (url, pageNo) => {
var options = {
url: url,
headers: {
'Authorization': `Token token=${apiToken}`
},
json: true,
page: pageNo
};
return new Promise((resolve, reject) => {
request.get(options, (err, resp) => {
err ? reject(err) : resolve(resp);
})
});
};
Using get() to loop and get all responses:
const getAll = (plannerId, timestamp, range) => {
const plannerBookingsUrl = new URL(
`/api/planners/${plannerId}/bookings?since=${timestamp}&range=${range}`,
baseUrl
);
let response = get(plannerBookingsUrl, 1);
let bookings = [];
bookings.push(response);
response.then(results => {
let moreRequests = true;
let currentPage = 1;
const totalPages = parseInt(results.headers['x-total-pages']);
while (moreRequests) {
if (currentPage < totalPages) {
nextBatch = get(plannerBookingsUrl, currentPage + 1);
bookings.push(nextBatch);
currentPage++;
} else {
moreRequests = false;
}
}
});
return Promise.all(bookings);
};
Main() where I'm using getAll(...):
const main = () => {
const response = getAll(
'11716',
'2020-02-27',
'7'
);
response.then(results => {
console.log(results);
.catch(error => console.log(error))
};
main();
This returns the initial promise but not the remaining promises.
What I'm really have a problem with is reading the first API, making the remainder and returning them all together to be using in my main function.
Any help would be much appreciated!
Thanks.
You could put all your fetching logic inside the while loop. The way you get your bookings is the same, except for the first time where you need to get a little more information on the amount of pages.
Accomplish this by making your function async and check the first time of the loop if the totalPages value is already known. If it's not, await the response and get the info from the headers, and otherwise just push the response to the bookings array.
const getAll = async (plannerId, timestamp, range) => {
const plannerBookingsUrl = new URL(
`/api/planners/${plannerId}/bookings?since=${timestamp}&range=${range}`,
baseUrl
);
let bookings = [];
let currentPage = 1;
let totalPages = null;
while (totalPages === null || currentPage < totalPages) {
let response = get(plannerBookingsUrl, currentPage);
if (totalPages === null) {
let results = await response;
totalPages = parseInt(results.headers['x-total-pages']);
}
bookings.push(response);
currentPage++;
}
return Promise.all(bookings);
};
The problem is that you are returning Promise.all(bookings) outside response.then callback, so at this point bookings contains only the first call get(plannerBookingsUrl, 1).
Here is a possible solution using async:
const getAll = async (plannerId, timestamp, range) => {
const plannerBookingsUrl = new URL(
`/api/planners/${plannerId}/bookings?since=${timestamp}&range=${range}`,
baseUrl
);
let response = get(plannerBookingsUrl, 1);
let bookings = [];
bookings.push(response);
const results = await response; // wait for results here
let moreRequests = true;
let currentPage = 1;
const totalPages = parseInt(results.headers['x-total-pages']);
while (moreRequests) {
if (currentPage < totalPages) {
nextBatch = get(plannerBookingsUrl, currentPage + 1);
bookings.push(nextBatch);
currentPage++;
} else {
moreRequests = false;
}
}
return Promise.all(bookings); // bookings now contains every next batch
};
adapt on main() function:
const main = async () => {
const results = await getAll(
'11716',
'2020-02-27',
'7'
);
...
};
main();

Asynchronous function is not printing any output except for null

I'm new to asynchronous code and I'm not quite sure why the code is not producing the expected output.
When the 'console.log(data.imdb_id)' is uncommented is prints the required ids one by one, but otherwise just prints a "null" from the 'console.log(err)' line.
How do I fill my promiseArray with the required values and return this array for later use?
var movie_ids = [
474350, 522938, 540901,
384018, 419704, 420818,
521029, 301528, 484641,
515195, 466272, 441282,
423204, 533642, 530385,
568012, 535544, 456740,
523077, 531309
]
async function insert_ids(movie_ids) {
var promiseArray = [];
for (var i = 1; i <= movie_ids.length; i++) {
promiseArray.push(new Promise((resolve, reject) => {
var url1 = "FIRST PART";
var url2 = "SECOND PART";
request(url1 + movie_ids[i] + url2, function (err, res, body) {
if (!err & res.statusCode == 200) {
var data = JSON.parse(body);
resolve(data.imdb_id);
// console.log(data.imdb_id); > This prints the correct ids
} else {
console.log(err); > Prints "null"
}
});
}));
};
await Promise.all(promiseArray).then(function(result) {
console.log(result);
});
}
insert_ids(movie_ids);

Why is my code not waiting for the completion of the function?

I am trying to read some data from a file and store it in a database.
This is part of a larger transaction and I need the returned ids for further steps.
async parseHeaders(mysqlCon, ghID, csv) {
var self = this;
var hIDs = [];
var skip = true;
var idx = 0;
console.log("Parsing headers");
return new Promise(async function(resolve, reject) {
try {
var lineReader = require('readline').createInterface({
input: require('fs').createReadStream(csv)
});
await lineReader.on('close', async function () {
console.log("done: ", JSON.stringify(hIDs));
resolve(hIDs);
});
await lineReader.on('line', async function (line) {
line = line.replace(/\"/g, '');
if (line.startsWith("Variable")) { //Variable,Statistics,Category,Control
console.log("found variables");
skip = false; //Ignore all data and skip to the parameter description.
return; //Skip also the header line.
}
if (!skip) {
var data = line.split(",");
if (data.length < 2) { //Variable section done return results.
console.log("Found sub?",line);
return lineReader.close();
}
var v = data[0];
var bidx = data[0].indexOf(" [");
if (bidx > 0)
v = data[0].substring(0, bidx); //[] are disturbing mysql (E.g.; Air temperature [�C])
var c = data[2];
hIDs[idx++] = await self.getParamID(mysqlCon, ghID, v, c, data);//, function(hID,sidx) { //add data in case the parameter is not in DB, yet.
}
});
} catch(e) {
console.log(JSON.stringify(e));
reject("some error occured: " + e);
}
});
}
async getParamID(mysqlCon,ghID,variable,category,data) {
return new Promise(function(resolve, reject) {
var sql = "SELECT ID FROM Parameter WHERE GreenHouseID="+ghID+" AND Variable = '" + variable + "' AND Category='" + category + "'";
mysqlCon.query(sql, function (err, result, fields) {
if(result.length === 0 || err) { //apparently not in DB, yet ... add it (Acronym and Machine need to be set manually).
sql = "INSERT INTO Parameter (GreenHouseID,Variable,Category,Control) VALUES ("+ghID+",'"+variable+"','"+category+"','"+data[3]+"')";
mysqlCon.query(sql, function (err, result) {
if(err) {
console.log(result,err,this.sql);
reject(err);
} else {
console.log("Inserting ",variable," into DB: ",JSON.stringify(result));
resolve(result.insertId); //added, return generated ID.
}
});
} else {
resolve(result[0].ID); //found in DB .. return ID.
}
});
});
}
The functions above are in the base class and called by the following code:
let headerIDs = await self.parseHeaders(mysqlCon, ghID, filePath);
console.log("headers:",JSON.stringify(headerIDs));
The sequence of events is that everything in parseHeaders completes except for the call to self.getParamID and control returns to the calling function which prints an empty array for headerIDs.
The console.log statements in self.getParamID are then printed afterward.
What am I missing?
Thank you
As you want to execute an asynchronous action for every line we could define a handler to do right that:
const once = (target, evt) => new Promise(res => target.on(evt, res));
function mapLines(reader, action) {
const results = [];
let index = 0;
reader.on("line", line => results.push(action(line, index++)));
return once(reader, "close").then(() => Promise.all(results));
}
So now you can solve that easily:
let skip = false;
const hIDs = [];
await mapLines(lineReader, async function (line, idx) {
line = line.replace(/\"/g, '');
if (line.startsWith("Variable")) { //Variable,Statistics,Category,Control
console.log("found variables");
skip = false; //Ignore all data and skip to the parameter description.
return; //Skip also the header line.
}
if (!skip) {
var data = line.split(",");
if (data.length < 2) { //Variable section done return results.
console.log("Found sub?",line);
return lineReader.close();
}
var v = data[0];
var bidx = data[0].indexOf(" [");
if (bidx > 0)
v = data[0].substring(0, bidx); //[] are disturbing mysql (E.g.; Air temperature [�C])
var c = data[2];
hIDs[idx] = await self.getParamID(mysqlCon, ghID, v, c, data);
}
});

handlebar templates are not populating in sequence in a grid

The UI of the application is a 4x3 grid. Each tile in the grid gets poplulated by a external handlebar template.
The seqence is like this -
Create an JSON object of required data. data =
{___:"",___:"",___:""...};
Push these objects into an Array called dataArray = [{},{},{},{}...];
Pass this arrya to a fucntion which renders the template createGrid(dataArray,callback);
The sequence in the template Function createGrid()
iterate over dataArray using forEach((val)=>{})
Pass val to AJAX call to render and return the template data using promises
The data gets loaded and template gets rendered but the problem is sometime the seqence of populated tiles is not correct. Tile one appears in the last row or tile 3 gets switch with tile 4 etc.
I expect the data to be populated according to the sequence of the content in the dataArray but it doesn't happen.
Before I used async:false AJAX call for generating the template and it worked. I know that is depricated and doesn't go with the pattern.
[problem 2] Another problem is, I am attaching some images and sound in the template and I am using another AJAX call to validate if files exists or not and attach the file path in the object that is being pushed in the dataArray. I don't know how to call that pathValidate function during object creating. If I use sync AJAX call then I can return the value but with sync call, I am lost
Now the code -
// file path validation method
let validatePath = (folder, index, extension) => { // file path validation method
let url = folder + index + extension;
return new Promise((resolve, reject) => {
let xhttp = new XMLHttpRequest();
xhttp.open('GET', url, true);
xhttp.onload = () => {
if (xhttp.status == 200) resolve(url);
else {
if (index != 11) resolve('img/' + "default" + extension);
else resolve('img/' + "11" + extension);
}
};
xhttp.onerror = () => {
reject(xhttp.statusText);
};
xhttp.send();
});
}
let getTemplateAjax = (url, cardData) => {
return new Promise((resolve, reject) => {
$.ajax({
url: url,
type: 'GET'
}).done((data) => {
let template = Handlebars.compile(data),
finalContent = template(cardData);
resolve(finalContent);
}).fail((err) => {
console.log(err + "can't find template");
reject(err);
});
});
}
let createTiles = (target, dataArray, callback) => {
dataArray.forEach((val, index) => {
getTemplateAjax('templates/card.hbs', val).then((data) => {
let tile = $('<div>', {
id: "tile_" + index,
class: "tiles-sub-grid-item"
}).css({
'grid-column': (index % 4) / (index / 3),
'grid-row': (index % 3) / (index / 4),
'border-radius': '3px',
'cursor': 'pointer',
'background': 'rgba(0, 0, 0, 0.8)',
'box-shadow': '0px 0px 10px rgba(0,0,0,0.5)'
});
tile.append(data);
tile.appendTo(target);
if (callback) callback(tile);
}).catch(() => {
console.log('sorry no template');
});
});
}
Calling these function into rendering the main menu
let contentArray = [],
imagePromises = [],
audioPromises = [];
for (let i = 0; i < 12; i++) {
imagePromises.push(validatePath("img/com/", i, ".svg "));
}
Promise.all(imagePromises)
.then((dataArray) => {
dataArray.forEach((val, index) => {
contentArray.push({
index: index + 1,
title: title[index],
description: description[index],
image: val,
audio: " ",
status: "",
url: ""
});
});
createTiles("#main-content", contentArray).then((data) => {
console.log(data);
data.on('click', () => {
let tileId = $(data).find('.card-index').text();
let tileTitle = $(data).find('.card-title').text();
if ((parseInt(tileId) - 1) == 2) {
bodyParts();
} else if ((parseInt(tileId) - 1) == 11) {
alexaCommands();
} else {
let message = $(data).find('.card-description').text();
responsiveVoice.speak(message);
}
});
});
})
.catch((e) => {
// handle errors here
console.log(e);
});

Confusion with promises

I am getting stuck with trying to get JavaScript promise to work as intended.
My code:
var p = new Promise(function(resolve, reject) {
for (var i = 0; i < pics_needed.length; i++) {
download_pics(pics_needed[i])
}
for (var i = 0; i < partners_pics_needed.length; i++) {
partners_download_pics(partners_pics_needed[i])
}
resolve('Success!');
})
p.then(function() {
AsyncStorage.setItem("database",responseText)
AsyncStorage.removeItem("time")
alert ("Success! \nYour update has been installed.")
go()
});
Both functions that are called in the for loop download pictures from a server. The problem is, the p.then part of the function is running before all the pictures are downloaded. How can I alter this so that the p.then part happens after all the downloading is complete?
What the functions do:
function download_pics (id){
var path = RNFS.DocumentDirectoryPath + '/' + id + '.jpg';
fetch('address of server ='+id)
.then((response) => response.text())
.then((responseText) => {
var pic_object = JSON.parse(responseText)
RNFS.writeFile(path, pic_object.response, 'base64')
});
}
As hinted in the comments - promises are not worthless if you're going to go behind their backs.
function download_pics (id){
var path = RNFS.DocumentDirectoryPath + '/' + id + '.jpg';
return fetch('address of server ='+id) // <--- NOTE: return here
.then((response) => response.text())
.then((responseText) => {
var pic_object = JSON.parse(responseText)
return RNFS.writeFile(path, pic_object.response, 'base64') // <-- and here
});
}
and then
var pics_promises = pics_needed.map(function(pic) {
return download_pics(pic);
});
var partners_pics_promises = partners_pics_needed.map(function(pic) {
return partners_download_pics(pic);
});
return Promise.all(pics_promises.concat(partners_pics_promises));
EDIT: added the RNFS.writeFile to the promise chain per #adeneo (I'm not familiar with RNFS).

Categories

Resources