node.js - using .map to enrich object - node.js

I have an object (set of product data), within some of those objects I have an array of images. For each of the images I need to call an API to get extra data, then I want to put this back into the original object.
The API call is by ID, that's working and I can get the data.
I think I have a problem with the async nature and I am missing something (probably obvious!)...
Thanks!
function fetchImages(products) {
var deferred = Q.defer();
var product_updates = [];
products.forEach(function (product, idx, array) {
var image_updates = [];
if (product.images.length > 0)
{
//var id = product.images.mediaId;
//console.log(id);
image_updates = product.images.map(function(task) {
return {
image_urls: getImage(task.mediaId)
}
});
console.log(image_updates);
product_updates.push(image_updates);
}
else
{
product_updates.push(product);
}
if (idx === array.length - 1)
{
//console.log(stock_updates);
deferred.resolve(product_updates);
}
});
return deferred.promise;
}
Here is the shortened "getImage" function...
function getImage(id)
{
// Request options
var options = {
url: cfg.base_url + (cfg.base_url[cfg.base_url.length - 1] != '/' ? '/' : '') + 'api/media/' + id,
.........
};
request(options, function (error, response, body) {
// Check status code
if (response.statusCode >= 200 && response.statusCode <= 299) {
let result = JSON.parse(body);
console.log(result);
return result;
} else {
console.log("Failed to fetch images updates");
}
}
);
}
I'm also unsure if "deferred.resolve(product_updates);" is done correctly.. seems to work but not 100% sure.
console.log for "image_update" returns:
[ { image_urls: undefined },
{ image_urls: undefined },
{ image_urls: undefined },
{ image_urls: undefined },
{ image_urls: undefined } ]
************ Amended fetchImages() function ***********
function fetchImages(products) {
const getImagesForProduct = function(product) {
return product.images.map(function(task) {
return {
product: product,
image_urls: getImage(task.mediaId)
}
});
}
const product_updates = products.map(getImagesForProduct)
return new Promise(function(resolve, reject)
{
resolve(product_updates);
});
}
This is more eloquent now... but still not got the promise needed?

In you example getImage makes and asynchronous call and therefore should return a promise. Many ways to return a promise - but the native promise object is the easiest (i suspect you could do this with Q too - but it has been a long time since I used that library).
function getImage(id)
{
// Request options
var options = {
url: cfg.base_url + (cfg.base_url[cfg.base_url.length - 1] != '/' ? '/' : '') + 'api/media/' + id,
.........
};
return new Promise(function(resolve, reject){
request(options, function (error, response, body) {
// Check status code
if (response.statusCode >= 200 && response.statusCode <= 299) {
let result = JSON.parse(body);
console.log(result);
resolve(result);
} else {
reject(error);
}
});
})
}
The other function could be written more eloquently too. Essentially:
// :: Product -> [{image_urls: Promise}]
const getImagesForProduct = function(product) {
return product.images.map(function(task) {
return {
image_urls: getImage(task.mediaId)
}
});
}
const product_updates = products.map(getImagesForProduct)
//=> [[{image_urls: Promise}]]
In this scenario you still need to wait on the promises to resolve. I suspect you could flatten the array or restructure the transformations to be less hairy - but it depends on what your other code needs

Here's the final function that deals with the promises:
function fetchImages(products) {
const getImagesForProduct = function(product) {
if (product.images && product.images.length === 0) {
return Promise.resolve(product)
}
const getImagesPromiseTasks = product.images.map(task => getImage(task.mediaId));
return Promise.all(getImagesPromiseTasks)
.then(retrievedUrls => {
product.image_urls = retrievedUrls
return product
})
}
return Promise.all(products.map(getImagesForProduct))
}

Related

Why on my NodeJS+Express REST API a promise calling my function fails while the same promise with setTimeout works?

I have a NodeJS+Express REST API method executing reverse geocoding (using Google's Maps API).
I'm trying to solve it with Promises but the 'then' is getting executed before my function returns with the answers from Google.
When testing the same code just calling a setTimeout, it works as expected. Please see comments in the code (simplify version).
app.get('/api/v1/events', verifyToken, async (req, res) => {
await db.poolPromise.then(pool => {
return pool.request()
.input('UserId', db.sql.UniqueIdentifier, res.authData.userId)
.input('DateFrom', db.sql.DateTime2(7), req.query.dateFrom)
.input('DateTill', db.sql.DateTime2(7), req.query.dateTo)
.output('UserIdAuthorized', db.sql.Bit)
.execute('sp')
}).then(result => {
let output = (result.output || {})
if (!output.UserIdAuthorized) {
res.sendStatus(403)
}
else if (result.recordset.length > 0) {
(new Promise( (resolve) => {
//resolve(123) // this one works as expected
//setTimeout(resolve, 3000, 'temp success') // this one works as expected
// *** this one get passed and the following then is being executed before it answers ***
resolve( getAddress_TEST(result.recordset) )
// **************************************************************************************
})).then(function (value) {
res.json(
{
meta: { count: 10 }, //this is just a sample
result: value // *** this one fails with undefined ***
})
})
} else {
res.sendStatus(404)
}
}).catch(err => {
res.sendStatus(500)
console.error(err)
})
});
const nodeGeocoder_options = {
provider: 'google',
apiKey: process.env.GOOGLE_API_KEY
}
async function getAddress_TEST(recordset) {
//sample recordset for debugging - as you dont have my database
recordset = [{'eventId':14205556,'Lat':54.57767,'Lon':-2.4920483},{'eventId':14205558,'Lat':54.57767,'Lon':-2.492048},{'eventId':14205579,'Lat':53.416908,'Lon':-2.952071},{'eventId':14205588,'Lat':52.644448,'Lon':-1.153185},{'eventId':14205601,'Lat':52.29174,'Lon':-1.532283},{'eventId':14205645,'Lat':52.644448,'Lon':-1.153185},{'eventId':14205801,'Lat':53.68687,'Lon':-1.498708},{'eventId':14206041,'Lat':51.471521,'Lon':-0.2038033},{'eventId':14206049,'Lat':51.471521,'Lon':-0.2038033},{'eventId':14206072,'Lat':51.471521,'Lon':-0.2038033}]
let geocoder = nodeGeocoder(nodeGeocoder_options)
let ps = []
for (var i = 0, length = recordset.length; i < length; i++) {
if (i == 0 || !(i > 0
&& recordset[i - 1].Lat == recordset[i].Lat
&& recordset[i - 1].Lon == recordset[i].Lon)) {
ps.push(new Promise(function (resolve) {
resolve(reverseGeocode(geocoder, recordset[i].Lat, recordset[i].Lon))
}))
} else {
ps.push('-')
}
}
await Promise.all(ps)
.then(function (values) {
for (var i = 0, length = values.length; i < length; i++) {
if (values[i] != '-') {
recordset[i].locationAddress = values[i]
} else {
recordset[i].locationAddress = recordset[i - 1].locationAddress
}
}
}).then(function () {
recordset.forEach(function (v) {
delete v.Lat
delete v.Lon
});
console.log(recordset)
return recordset
})
};
async function reverseGeocode(geocoder, lat, lon) {
let address = '+'
if (lat != 0 && lon != 0) {
await geocoder.reverse({ lat: lat, lon: lon })
.then(res => {
address = res[0].formattedAddress
})
.catch(err => {
console.error(err)
});
}
return address
};
I'm sure it is something simple that I'm missing here...
The basic problem is that your getAddress_TEST function returns a promise that fulfills with nothing (undefined), because it does not contain a return statement. The return recordset is in a then() callback, from where it affects the promise resolution of the awaited promise, but that result is thrown away.
If you want to use async/await, you should get rid of any new Promise and then calls:
app.get('/api/v1/events', verifyToken, async (req, res) => {
try {
const pool = await db.poolPromise
const result = await pool.request()
.input('UserId', db.sql.UniqueIdentifier, res.authData.userId)
.input('DateFrom', db.sql.DateTime2(7), req.query.dateFrom)
.input('DateTill', db.sql.DateTime2(7), req.query.dateTo)
.output('UserIdAuthorized', db.sql.Bit)
.execute('sp')
let output = (result.output || {})
if (!output.UserIdAuthorized) {
res.sendStatus(403)
} else if (result.recordset.length > 0) {
const value = await getAddress_TEST(result.recordset)
res.json({
meta: { count: 10 }, //this is just a sample
result: value
})
} else {
res.sendStatus(404)
}
} catch(err) {
res.sendStatus(500)
console.error(err)
}
});
const nodeGeocoder_options = {
provider: 'google',
apiKey: process.env.GOOGLE_API_KEY
}
async function getAddress_TEST(recordset) {
const geocoder = nodeGeocoder(nodeGeocoder_options)
const ps = recordset.map((record, i) => {
if (i == 0 || !(i > 0
&& recordset[i - 1].Lat == record.Lat
&& recordset[i - 1].Lon == recordLon)) {
return reverseGeocode(geocoder, recordset[i].Lat, recordset[i].Lon))
} else {
return '-'
}
});
const values = await Promise.all(ps)
// ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
for (var i = 0, length = values.length; i < length; i++) {
if (values[i] != '-') {
recordset[i].locationAddress = values[i]
} else {
recordset[i].locationAddress = recordset[i - 1].locationAddress
}
}
recordset.forEach(function (v) {
delete v.Lat
delete v.Lon
});
console.log(recordset)
return recordset
// ^^^^^^^^^^^^^^^^
}
async function reverseGeocode(geocoder, lat, lon) {
if (lat != 0 && lon != 0) {
const res = await geocoder.reverse({ lat: lat, lon: lon })
return res[0].formattedAddress
}
return '+'
}

How to consume a RESTful API in Node.js

I'm new to Node.js and I'm creating a simple pagination page. The REST API works fine, but consuming it has left me in limbo.
Here is the REST API (other parts have been taken out for brevity)
const data = req.query.pageNo;
const pageNo =
(typeof data === 'undefined' || data < 1) ? 1 : parseInt(req.query.pageNo);
let query = {};
const total = 10;
query.skip = (total * pageNo) - total;
query.limit = total;
try {
const totalCount = await Users.countDocuments();
const pageTotal = Math.ceil(totalCount / total);
const users = await Users.find({}, {}, query);
return res.status(200).json(users);
} catch (error) {
console.log('Error ', error);
return res.status(400).send(error)
};
};
When I return the json with just the 'users' object, like so return res.status(200).json(users); the page renders correctly, but when I pass in other objects like what I have in the code, it fails. This is how I'm consuming the API:
const renderHomepage = (req, res, responseBody) => {
let message = null;
if (!(responseBody instanceof Array)) {
message = 'API lookup error';
responseBody = [];
} else {
if (!responseBody.length) {
message = 'No users found nearby';
}
}
res.render('users-list', {
title: 'Home Page',
users: responseBody,
message: message
});
}
const homelist = (req, res) => {
const path = '/api/users';
const requestOptions = {
url: `${apiOptions.server}${path}`,
method: 'GET',
json: true,
};
request(
requestOptions,
(err, {statusCode}, body) => {
if (err) {
console.log('Ther was an error ', err);
} else if (statusCode === 200 && body.length) {
renderHomepage(req, res, body);
} else if (statusCode !== 200 && !body.length) {
console.log('error ',statusCode);
}
}
);
}
I've searched extensively on both here and other resources but none of the solutions quite answers my question. I hope someone could be of help

How to get code to execute in order in node.js

I am trying to finish my script, but for some reason i don't know, it refuses to execute in the order i put it in.
I've tried placing a 'wait' function between the JoinRequest update function and the following code, but when run, it acts as if the function call and wait function were the other way round, countering the point of the wait().
const Roblox = require('noblox.js')
var fs = require('fs');
var joinRequests = []
...
function wait(ms) {
var d = new Date();
var d2 = null;
do { d2 = new Date(); }
while(d2-d < ms*1000);
};
...
function updateJReqs() {
Roblox.getJoinRequests(4745601).then((array) => {
var i;
var final = [];
for(i = 0; i < array.length; i++) {
final.push(array[i].username);
};
if(final === '') {
final = '-None';
};
joinRequests = final
console.log('Updated join requests.')
});
}
function check() {
setTimeout(() => {
fs.readFile('Request.txt',encoding = 'utf-8', function(err, data) {
if (err) {
check();
} else {
updateJReqs(); //for some reason this function is executed alongside the below, not before it.
// Tried putting wait(x) in here.
console.log('Request received: ' + data)
var solution = joinRequests
console.log('Fuffiling request with ' + solution)
fufillRequest(solution)
fs.unlink('Request.txt', function(err) {
if(err) throw err;
});
check();
}
});
}, 400)
}
check();
The script is supposed to wait until a file is created (accomplished), update the list of join requests (accomplished) and then create a new file with the list of join requests in(not accomplished).
if I understand your code you work with async code, you need to return a promise in updateJReqs and add a condition of leaving from the function because you have an infinite recursion
function updateJReqs() {
return new Promise(resolve => {
Roblox.getJoinRequests(4745601).then((array) => {
var i;
var final = [];
for(i = 0; i < array.length; i++) {
final.push(array[i].username);
};
if(final === '') {
final = '-None';
};
joinRequests = final
console.log('Updated join requests.')
resolve();
});
}
}
async function check() {
setTimeout(() => {
fs.readFile('Request.txt',encoding = 'utf-8', function(err, data) {
if (err) {
await check();
} else {
await updateJReqs();
// Tried putting wait(x) in here.
console.log('Request received: ' + data)
var solution = joinRequests
console.log('Fuffiling request with ' + solution)
fufillRequest(solution)
fs.unlink('Request.txt', function(err) {
if(err) throw err;
});
// you dont have an exit from your function check();
return 'Success';
}
});
}, 400)
}
check().then(res => console.log(res));

multiple promises in api server node returns null

I have some problems with the multiple promises in my code. There is no way to return to items who are not in the database. I changed the code multiple times but no luck. The only data it returns is "datas": [
null,
null
]
This is my code
var start = function(offset, entry) {
return new Promise(function(resolve, reject) {
rp('************' + entry).then(function(repos) {
resolve(repos);
}).catch(function(err) {
reject(err);
});
});
};
var findnewones = function(iten) {
return new Promise(function(resolve, reject) {
return Promise.all(iten.items.map(function(ndtrcitem) {
return new Promise(function(resolve, reject) {
Items.findOne({"metadata.trcid": ndtrcitem.metadata.trcid}).exec(function(err, doc) {
if (!doc) {
resolve(ndtrcitem);
}
});
})
})).then(datas => {
resolve(datas);
});
})
}
exports.find = function(req, res, next) {
var ndite = ["locations", "events"];
var items = [];
return Promise.all(ndite.map(function(entry) {
return start(0, entry).then(function(res) {
for (i = 0; i <= res.count; i += 10) {
return start(i, entry).then(function(iten) {
findnewones(iten).then(function(dat) {
items.push(dat);
});
});
}
return items;
})
})).then(datas => {
res.json({datas});
});
}
I think because the for loop there is synchronous and it's not waiting for the start() promise to resolve.
for (i = 0; i <= res.count; i += 10) {
return start(i, entry).then(function(iten) {
findnewones(iten).then(function(dat) {
items.push(dat);
});
});
}
I have replaced it with async/await, don't know if it will work right away, I am just providing you with a hint in this very complicated promise chain. If it or any variation of it works please update this answer.
exports.find = function (req, res, next) {
var ndite = ["locations", "events"];
var items = [];
return Promise.all(ndite.map(function (entry) {
return start(0, entry)
.then(async function (res) {////// this
for (i = 0; i <= res.count; i += 10) {
await start(i, entry).then(function (iten) { ////this
findnewones(iten).then(function (dat) {
items.push(dat);
});
});
}
return items;
})
})).then(datas => {
res.json({
datas
});
});
}

Nodejs loop through same API with different parameters

I trying to loop through same API result and if the API result is NULL then I want loop through it again few times (i.e 4-5 times) with different parameters and if it's reached the 5th time. I want to exit the loop. The code I'm trying is see below:
var roads = 1000;
var findResult = true;
var loop = 0;
while (findResult) {
result = APIResult(rarray, roads);
if (result !== null) {
findResult = false; // stop the loop
} else if (loop == 5) {
findResult = false; // stop the loop
} else {
roads = roads * 10;
loop++;
}
}
function APIResult(rarray, roads) {
request.post(
env.TEST_URL + 'test/',
{
json: {
//...
roads: roads,
//..
},
},
function(error, response, body) {
if (!error && response.statusCode == 200) {
return JSON.parse(body.rows[0].result);
}
});
}
I'm even tried adding Q promise but didn't worked, any idea how to do it?
Your APIResult function doesn't return anything. This function is asynchronous, so it should return promise or use a callback.
Your code result = APIResult(rarray, roads); sets to result variable undefined value. I think, async/await style for implementing asynchronous JS features will work for you.
Current last Node.js version is 8.1. It has native support for async/await. There is an example, how you can implement your code:
async function main() {
var roads = 1000;
var findResult = true;
var loop = 0;
while (findResult) {
try {
result = await APIResult(rarray, roads);
} catch (e) {
//APIResult reject promise branch
}
if (result !== null) {
findResult = false; // stop the loop
} else if (loop == 5) {
findResult = false; // stop the loop
} else {
roads = roads * 10;
loop++;
}
}
}
async function APIResult(rarray, roads) {
return new Promise((res, rej) => {
request.post(
env.TEST_URL + 'test/',
{
json: {
//...
roads: roads,
//..
},
},
function(error, response, body) {
if (error) return rej(error);
if (response.statusCode === 200) return res(JSON.parse(body.rows[0].result));
});
});
}
main();

Resources