Within a node.js app, I'm trying to look up a list of products from an API, then get the thumbnail image for the first item only.
Within my code getOrderDetails is a promise, and then if it's the first product, it calls function getImage which is also a promise.
However when I run the code the orderDetails is populated without the thumbnail - so it looks like the code isn't waiting for the getImage function to resolve.
Please can someone explain what I have done wrong?
Thank you in advance!
if (siteConfig.storeIntegration == 'Cart') {
getOrderDetails(restPathOrder)
.then(function(res) {
var orderDetails = '';
for (i = 0; i < res.items.length; i++) {
productId = res.items[i]['productId'];
thumbnail = '';
if (i == 0) {
getImage(productId, restApiKey)
.then(function(res) {
thumbnail = res;
console.log('thumbnail:' + res);
})
}
orderDetails = orderDetails + res.items[i]['quantity'] + ',' + res.items[i]['name'] + ',' + productUrl + ',' + thumbnail;
}
console.log(orderDetails);
})
}
function getOrderDetails(restPath) {
return new Promise((resolve, reject) => {
request(restPath, function (error, response, body) {
if (!error && response.statusCode == 200) {
restData = JSON.parse(body);
resolve(restData)
}
})
})
}
function getImage(productId, restApiKey) {
return new Promise((resolve, reject) => {
var restPathProduct = storeURL + restApiKey + productId;
request(restPathProduct, function (error, response, body) {
if (!error && response.statusCode == 200) {
restData = JSON.parse(body);
thumbnail = restData.image[0];
resolve(thumbnail);
}
})
})
}
Might I suggest using new await syntax?
async main() {
if (siteConfig.storeIntegration == 'Cart') {
let res = await getOrderDetails(restPathOrder);
var orderDetails = '';
for (i = 0; i < res.items.length; i++) {
productId = res.items[i]['productId'];
let thumbnail = '';
if (i == 0) {
thumbnail = await getImage(productId, restApiKey);
}
orderDetails += res.items[i]['quantity'] + ',' + res.items[i]['name'] + ',' + productUrl + ',' + thumbnail;
}
console.log(orderDetails);
}
}
#FrankerZ is right that async/await makes the code much cleaner.
I'm unsure on your business logic, where productUrl comes from.
The solution is to wrap the whole block of code in a promise, get the image for the first order, and in the then of getImage, then you can process the order details.
Here is my attempt, with some minor refactors. Hopefully it can steer you in the right direction.
function process() {
// Wrap everything inside a promise, we then resolve with the order details
// Resolve is what gets returned in the .then((resolvedData)), reject is what gets returned in the .catch((rejectedThings))
return new Promise((resolve, reject) => {
// Get the order details
getOrderDetails(restPathOrder)
.then(function(orderRes) {
// Handle no items
if (!orderRes.items || orderRes.items.length === 0) {
return reject("No items") // You can deal with this in the catch
}
// Get first item
const firstOrder = orderRes.items[0];
// Fetch the first thumbnail then proceed to fetch the rest of the order details.
getImage(firstOrder.productId, restApiKey)
.then(function(thumbnail) {
let orderDetails = '';
// Build order details body
// N.B I'm not sure how you want orderDetails to appear, personally I would use a map instead of a forEach.
orderRes.items.forEach((item) => {
orderDetails = orderDetails + item.quantity + ',' + item.name + ',' + productUrl + ',' + thumbnail;
})
// Then resolve with the order details
return resolve(orderDetails);
})
})
})
}
// You can then call process as a promise with the order details
process().then(function(orderDetails) => console.log(orderDetails))
Related
I'm currently trying to build a discord bot, and I want to use a database for some aspects of it. Currently, I'm trying to add a command that would return the names of all the tables I have in the database, and for the most part I have it down.
The part that I'm struggling with is actually getting the names back out as a var. Every guide and stackoverflow question that I've been able to find on it assume that you just want to get that result and then print it to the console, but I need to return it back to the method that called this.
My previous attempt was setting an outside variable and using a promise to wait for it to change, but I couldn't get that to work, most likely because I don't fully understand how Promises work. My current attempt uses setTimeout() to check, but that just returns the asyncID of either the first or second iteration.
Any help either in making either of these work or completely scrapping them and doing this a different way is very welcome.
Previous code:
function listTables() {
db.query('SELECT table_name FROM information_schema.tables WHERE table_schema=\'' + dbName + '\'', (error, results) => {
if(error) throw error;
let temp = '';
results.forEach((item) => {
temp += item.table_name + ', ';
}); temp = temp.slice(0, -2);
setReturn(temp);
});
let out = checkReturn().then((value) => {
return value();
}).catch((error) => {
console.log(error);
return '';
});
returnValue = null;
return out;
}
var returnValue = null;
function setReturn(value) {
returnValue = value;
}
async function checkReturn() {
console.log('Checking Return: ' + returnValue);
let promise = new Promise((resolve, reject) => {
if(returnValue === null) reject('Var not set');
else resolve(returnValue)
});
return await promise;
}
Current Code:
function listTables() {
setReturn(null);
db.query('SELECT table_name FROM information_schema.tables WHERE table_schema=\'' + dbName + '\'', (error, results) => {
if(error) throw error;
let temp = '';
results.forEach((item) => {
temp += item.table_name + ', ';
}); temp = temp.slice(0, -2);
setReturn(temp);
});
return checkReturn();
}
var returnValue = null;
function setReturn(value) {
returnValue = value;
}
function checkReturn() {
console.log('Checking Return: ' + returnValue);
if(returnValue === null) {
return setTimeout(checkReturn, 50);
} else {
return returnValue;
}
}
You need to modify the listTables function to return a promise.
function listTables() {
return new Promise((resolve, reject) => {
db.query('SELECT table_name FROM information_schema.tables WHERE table_schema=\'' + dbName + '\'', (error, results) => {
if(error) {
reject(error);
return;
}
let temp = '';
results.forEach((item) => {
temp += item.table_name + ', ';
}); temp = temp.slice(0, -2);
resolve(temp);
});
});
}
// Usage of `listTables()`
listTables()
.then(result -> {
// Do process result
});
Hi I have a for loop in my node js application which calls an async function. I want to check a value and decide whether a customer is found or not. But the loop iterates until the last element. Hence my error loop is not working. I want the loop to check the response and then iterate the next loop.
for loop:
for (let i = 0; i < customerlookupresponse.data.length; i++) {
var customer = customerlookupresponse.data[i];
if (customer != undefined) {
console.log("customer.id :: " + customer.id)
var accountlookUpData = {
customerId: customer.id
};
customerAccountLookUpRequest(accountlookUpData).then(data => {
console.log("----" + i + " -- " + data);
if (data && data.status === 1) {
resolve(data);
return;
}else{
reject({
status: 404,
message: "Customer not found"
});
return;
}
});
} else {
reject({
status: 404,
message: "Customer not found"
});
return;
}
}
the async function:
async function customerAccountLookUpRequest(customerLookUpData) {
var accountLookUp = config.app.url;
let data = await axios.get(accountLookUp).then(accountLookUpResult => {
for (i = 0; i < accountLookUpResult.data.length; i++) {
var requestaccount = accountLookUpResult.data[i].accountNumber;
if (requestaccount == customerLookUpData.lookupAccount) {
accountLookUpResult.data[i].customerId = customerLookUpData.customerId;
accountLookUpResult.data[i].status = 1;
return accountLookUpResult.data[i];
}
}
});
return data;
}
I am new to node js and trying to understand the concept of async await. Please help.
An async function waits for a Promise to return. The function that has the loop should be declared as async and the customerAccountLookUpRequest function should return a promise. Then use the await operator to call the function. Simple example:
class some_class {
constructor() {
}
async my_loop() {
let _self = this;
for (let i = 0; i < customerlookupresponse.data.length; i++) {
let data = await _self.customerAccountLookUpRequest(accountlookUpData);
console.log("----" + i + " -- " + data);
}
}
customerAccountLookUpRequest(customerLookUpData) {
return new Promise((resolve, reject) => {
axios.get(accountLookUp).then(accountLookUpResult => {
resolve(accountLookUpResult);
});
});
}
}
I have a question about some code that I have. I'm going to post code and break it down below in a second, however i'd like to explain it in advance. My code is a function called getPing, its in a node server and its goes to a website and give me back an array of objects. It sorts through those objects, and based on the lowest number (ping) It pushes them into an array. Once everything is finished, it will sort through the array and pick a random object. That object as you will see in the code is called selectedserver, it then takes that object and then it SHOULD resolve it, and send the data back to the client. Note that all of this is happening in the same file.
As you will see in a second, once a certain condition is met there is a return, but right above that there is a resolve() that I can't seem to get working. Here is my code.
First, we'll start with where the promise starts.
var getPing = function (id,index) {
return new Promise(function (resolve, reject) {
var keepAliveAgent = new https.Agent({ keepAlive: true })
options.agent = keepAliveAgent
index = index || 0;
var r = https.request(options, function (res) {
var data = []
res.on('data', function (d) {
data.push(d)
}).on('end', function () {
var buf = Buffer.concat(data)
var encodingheader = res.headers['content-encoding']
if (encodingheader == 'gzip') {
zlib.gunzip(buf, function (err, buffer) {
var o = JSON.parse(buffer.toString())
// o is what is returned
if (o.TotalCollectionSize - 20 <= index) {
console.log(o.TotalCollectionSize - 20, '<=', index)
var selectedserver = games.gameservers[Math.floor(Math.random() * games.gameservers.length)]
console.log(selectedserver)
resolve(selectedserver)
return;
}
if (index < o.TotalCollectionSize) {
index = index + 10;
console.log(index, o.TotalCollectionSize)
o.Collection.sort(function (a, b) {
return a.Ping > b.Ping
})
if (typeof (o.Collection[0]) != "undefined") {
var playerscapacity = o.Collection[0].PlayersCapacity.charAt(0)
if (playerscapacity != o.Collection[0].Capacity) {
games.gameservers.push(o.Collection[0])
}
}
getPing(id, index)
}
})
}
})
})
r.end()
//reject('end of here')
})}
As you can see here:
if (o.TotalCollectionSize - 20 <= index) {
console.log(o.TotalCollectionSize - 20, '<=', index)
var selectedserver = games.gameservers[Math.floor(Math.random() * games.gameservers.length)]
console.log(selectedserver)
resolve(selectedserver)
return;
}
Once the o.Totalcollectionsize - 20 is <= to the index, Its suppose to take the games that it pushed into the games.gameservers array, and its suppose to resolve it. The code works besides the resolve part, I know this because all of the console.log's in that code work.
Now this is my node server, that's supposed to send the resolved data BACK to the client.
var server = io.listen(47999).sockets.on("connection", function (socket) {
var ip = socket.handshake.address;
var sid = socket.id;
console.log("Connection from " + ip + "\n\tID: " + sid);
http.createServer(function (req, res) {
res.setHeader('Content-Type', 'application/json');
res.setHeader("Access-Control-Allow-Origin", "*");
res.setHeader("Access-Control-Allow-Headers", "X-Requested-With")
//res.writeHead(200, { 'Content-Type': 'text/plain' });
var data = []
if (req.method == "POST") {
res.writeHead(200, { 'Content-Type': 'text/plain' });
req.on('data', function (dat) {
data.push(dat)
})
req.on('end', function () {
var gamedata = Buffer.concat(data).toString();
var game = JSON.parse(gamedata)
getPing(game.placeId, 0).then(function (r) {
console.log(r)
res.end(JSON.stringify(r))
}).catch(function (e) {
console.log(e)
})
console.log(game.placeId)
})
}
}).listen(6157)
console.log('server running')})
As you can see, in my node server when you send a post request to it, it will start the promise.
getPing(game.placeId, 0).then(function (r) {
console.log(r)
res.end(JSON.stringify(r))
}).catch(function (e) {
console.log(e)
})
However, it never gets to this point. I'm new to promises so I'm not where I'm going wrong here. I've tried everything (or so i thought). I would like to learn how promises fully work, because clearly I don't understand them enough. I'm just trying to get this to work at this point.
const https = require('https');
const zlib = require("zlib");
function downloadPage(url) {
return new Promise((resolve, reject) => {
https.get(url,(res)=>{
let raw = "";
let gunzip = res.pipe(zlib.createGunzip());
gunzip.on('data',(chunk)=>{
raw += chunk;
})
.on('end',()=>{
resolve(raw);
})
.on('error',(err)=>{
reject(err);
})
})
});
}
async function myBackEndLogic() {
const html = await downloadPage('https://api.stackexchange.com/2.2/search?page=1&pagesize=2&order=desc&sort=relevance&intitle=javascript%2Bfilter&site=stackoverflow')
return html;
}
myBackEndLogic().then((data)=>console.log(data));
Try something like this.
So, I'm using the github APIv3 to get some data for a project I'm doing. I have to be sure that the results from my request are tight cause it's for an article I'm writing on code smells.
I have to know which classes developers are messing with so my data needs to be perfect and verifiable.
I made this HTTP request to the github API to get the data. The problem is sometimes the data gathered is A, sometimes it is B. Is there a way I can be SURE the data gathered is the full and complete data that can be gathered from the API? (the errors in the console are always the same).
for (let i = 0; i < devInfo.length; i++) {
let dev = devInfo[i];
let project = dev.project;
let name = dev.dev;
let repo = reposData.find(e => e.repo == project);
let maxDate = repo.maxDate;
let url = `repos/${repo.owner}/${project}/commits`;
console.log("Dev: " + name + " Project: " + project);
client.get(url, {
until: maxDate,
author: name
}, function(
err,
status,
body,
headers
) {
if (err != null) {
console.log(err);
}
if (Array.isArray(body)) {
body.forEach(commitInfo => {
let date = new Date(commitInfo.commit.author.date);
if (commitInfo.author != null) {
client.get(`${url}/${commitInfo.sha}`, {}, function(
err,
status,
body,
headers
) {
if (err != null) {
console.log(err);
}
if (body != null || body != undefined) {
if (Array.isArray(body.files)) {
body.files.forEach(file => {
let fnArr = file.filename.split("/");
let javaClass = fnArr.find(e => e.match(/.java/));
if (validPojects.some(e => e == project)) {
if (javaClass != null) {
let classArr = javaClass.split(".");
let className = classArr[0];
fs.appendFileSync(
"filesMoved3.csv",
project +
"," +
name +
"," +
className +
"," +
date +
"\n"
);
}
}
});
}
}
});
}
});
}
});
}
I have a method in one of my controller. The purpose of the controller, is print an array of urls using webshot package.
This is the code in question:
router.post('/capture', function (req, res, next) {
//Check params remove
var json = JSON.parse(req.body.data);
var promise = new Promise(function (resolve, reject) {
var totalImages = Object.keys(json).length;
var arrayListUrlImages = new Array(totalImages);
var counter = 0;
var completeDir = dir + ''; //Directory URL
for (var value of json) {
var url = 'http://example.com/' + id + '/' + value.anotherValue;
var folder = completeDir + id + '/' + value.anotherValue + '.jpg';
//Options for capturing image
var options = {
renderDelay: 1000,
quality: 100,
phantomConfig:
{
'local-to-remote-url-access': 'true',
'ignore-ssl-errors': 'true'
}
};
var anotherValue = value.anotherValue;
(function (anotherValue) {
webshot(url, folder, options, function (err) {
// screenshot now saved
if (err === null) {
var urlImage = "http://example.com/images/" + id + "/" + anotherValue + ".jpg";
arrayListUrlImages.push(urlImage);
counter++;
console.log("Counter: " + counter);
if (counter === totalImages) {
resolve(arrayListUrlImages);
}
}
else {
reject(err);
}
});
})(anotherValue);
}
}).then(function (arrayImages) {
res.send(arrayImages);
}).catch(function (errorVale) {
res.send(null);
});
});
This code is working without problems... but I would like to do better. I don't know how many URLs need to check (this is important detail because I need to do a for each or similar).
I have read about async package... Is better option move this code to something like async.parallel? Can I use yield in my code?
Thanks!
Since you are using Promise, I recommend Promise.all.
It returns a promise that resolves when all of the promises in the iterable argument have resolved, or rejects with the reason of the first passed promise that rejects.
Seems like it solves your problem.
Example:
downloadOne = url => new Promise(resolve => {
webshot(url, ....., (err, res) => resolve(res));
})
router.post('/capture', function (req, res, next) {
var urls = JSON.parse(req.body.data);
Promise.all(urls.map(downloadOne)).then(req.send);
}
This is an example of code flow based on inner functions:
router.post('/capture', function (req, res, next) {
// Definitions
// Load image
function loadImage(value) {
var url = 'http://example.com/' + id + '/' + value.anotherValue;
var folder = completeDir + id + '/' + value.anotherValue + '.jpg';
//Options for capturing image
var options = {
renderDelay: 1000,
quality: 100,
phantomConfig:
{
'local-to-remote-url-access': 'true',
'ignore-ssl-errors': 'true'
}
};
return webshotPromise(url, folder, options);
}
// Load whebshot as a promise
function webshotPromise(url, folder, options) {
return new Promise((resolve, reject) => {
webshot(url, folder, options, function (err) {
if (err) {
reject(err);
}
var urlImage = "http://example.com/images/" + id + "/" + anotherValue + ".jpg";
resolve(urlImage);
}
});
}
// The method flow
const json = JSON.parse(req.body.data);
// Get json keys and iterate over it to load
Promise.all(
Object.getOwnPropertyNames(json).map(key => loadImage(json[key]))
)
// Got list of urls
.then((list) => {
res.json(list);
}, (error) => {
console.error(error);
res.json(null);
});
});
You don't need to use async for such simple example. Use native promises:
router.post('/capture', function (req, res, next) {
//Check params remove
const json = JSON.parse(req.body.data);
Promise.all(Object.getOwnPropertyNames(json).map((key) => {
var value = json[key];
var url = 'http://example.com/' + id + '/' + value.anotherValue;
var folder = completeDir + id + '/' + value.anotherValue + '.jpg';
//Options for capturing image
var options = {
renderDelay: 1000,
quality: 100,
phantomConfig:
{
'local-to-remote-url-access': 'true',
'ignore-ssl-errors': 'true'
}
};
return new Promise((resolve, reject) => {
webshot(url, folder, options, function (err) {
if (err) {
reject(err);
return;
}
var urlImage = "http://example.com/images/" + id + "/" + anotherValue + ".jpg";
resolve(urlImage);
}
});
}))
.then((listOfUrls) => {
res.json(listOfUrls); // List of URLs
}, (error) => {
console.error(error);
res.json(null);
});
});
Honestly, your code looks fine.
If you are not going to add more logic here, leave it as it is.
What can be done better, is migrating it to ES6 syntax and extracting anotherValue function but I am not aware if this applicable for your case.