Javascript - Promises and forEach - javascript

I am trying to fill the array with avatarIcon node from parsed xmls but my final array in res.view is empty plus the execution doesn't seem to reach return res.view function. How do I do this correctly?
function parseXML(xml) {
var parsed
parseString(xml, { explicitArray: false }, function(err, result) {
parsed = result
})
return parsed
}
function findUsers() {
return new Promise(function(resolve, reject) {
User.find().exec(function(err, sids) {
resolve(sids)
})
})
}
avatars: function(req, res) {
var arr = []
findUsers().then(function(result) {
result.forEach(function(el) {
getProfileXML(el.sid).then(function(result) {
arr.push(parseXML(result).profile.avatarIcon)
})
})
return res.view('users', {
users: arr
})
})
}

You can use Promise.all to wrap a collection of promises. Here untested code to demonstrate the use, where the result of the expression is a promise too:
return findUsers().then(function(result) {
var promises = result.map(function(el) {
return getProfileXML(el.sid);
});
return Promise.all(promises);
}).then(function(values) {
var arr = values.map(function(v) {
return parseXML(v).profile.avatarIcon;
});
return res.view('users', {
users: arr
})
})

Related

problems with asynchronous data recording

there is a function code:
function fillingDataInCategory(categories, url) {
let categoriesData = [];
for (let i = 0; i < categories.length; i++) {
conn.query(`SELECT product_id
FROM products
WHERE product_category_id = ${categories[i].id}`, (err, productInCategory) => {
if(err) {
console.log(err);
fs.writeFileSync('api-reports-error-log.txt',
`${fs.readFileSync('api-reports-error-log.txt')}\n${url}: ${err} ${new Date().toLocaleDateString()}`);
} else {
console.log(productInCategory);
categoriesData.push({category: categories[i].id, productInCategory: productInCategory.length});
}
});
}
}
The problem is that an empty categoriesData array is returned due to asynchronous writing.
I haven't worked with asynchrony much, so I'll be happy to get any help.
I don't see a return at the end of your function, but I assume you want to return categoryData
I think that you are currently calling your function like this:
function myFunc() {
// Do stuff
let categoriesData = fillingDataInCategory(categories, "myurl");
console.log(categoriesData);
// Do stuff
}
First of all, I advise you to use forEach instead of for();
I suggest you to use a promise. And as you do multiple query in one function, use Promise.all
If the library you use to make your mysql calls allows it, use promises directly, but if it only allows you to make callbacks, convert that callback to a promise like this:
function makeRequest(categorie) {
return new Promise((resolve, reject) => {
conn.query(`SELECT product_id FROM products WHERE product_category_id = ${categorie.id}`, (err, productInCategory) => {
if (err) {
reject(err) //reject at error
} else {
resolve({category: categorie.id, productInCategory: productInCategory /* You can put your .length here */}); //resolve with your result
}
});
});
}
Here are the code that i wrote for you. I simulate your call do conn.query() with a delay of 1 sec
const fs = require('fs');
/* SIMULATE conn.query async call */
let conn = {
query: (text, callback) => {
setTimeout(callback(null, [1, 2, 3, 4] /* Return an fake array of result of length 4 */), 1000);
}
}
function makeRequest(categorie) {
return new Promise((resolve, reject) => {
conn.query(`SELECT product_id FROM products WHERE product_category_id = ${categorie.id}`, (err, productInCategory) => {
if (err) {
reject(err) //reject at error
} else {
resolve({category: categorie.id, productInCategory: productInCategory.length}); //resolve with your result
}
});
});
}
function fillingDataInCategory(categories, url) {
return new Promise((resolve, reject) => {
let promiseList = [] //Make a array to fill with pending promise
categories.forEach((categorie) => {
promiseList.push(makeRequest(categorie)); // Adding a new pending request with the categorie inside the array
})
resolve(Promise.all(promiseList)); // Promise.all return a promise that take a list of pending promise
});
}
function myFunc() {
fillingDataInCategory([{id: 1}, {id: 2}, {id: 3}], "myurl").then((categorieData) => {
console.log(categorieData); //Now you get the resolve of the Promise.all
//Do your post work
}).catch((err) => { // If one of all the requests of the Promise.all array throw an error, then all requests fails
//Do error stuffs
});
// Do stuff
}
myFunc();
and i get this :
➜ node test-stackoverflow.js
[
{ category: 1, productInCategory: 4 },
{ category: 2, productInCategory: 4 },
{ category: 3, productInCategory: 4 }
]

TypeError: someobject.somefunction(...).then is not a function

I have created a utility function for getting the total size of the webtable using protractor and javascript.
this.getTableSize = function(tableElement, rowSelector, columnSelector){
return {
row: tableElement.all(rowSelector).count(),
column : tableElement.all(columnSelector).count()
}
};
However on using the same function , i am geeting the error:
tableActions.getTableSize(table,by.css("tr"),by.css("th")).then(function(obj){
console.log(obj);
})
The error which i am getting is :
TypeError: tableActions.getTableSize(...).then is not a function
You need to correct your method to handle the promises correctly.
I assume that tableElement.all(rowSelector).count() returns a promise else you will have to handle the callbacks;
this.getTableSize = function (tableElement, rowSelector, columnSelector) {
return Promise.all([tableElement.all(rowSelector).count(), tableElement.all(columnSelector).count()]).then(function(data) {
return {
row: data[0],
column: data[1]
}
})
};
tableActions.getTableSize(table, by.css("tr"), by.css("th")).then(function (obj) {
console.log(obj);
})
Promise.all does not return the array of resolved data with bluebird promises so use.
this.getTableSize = function (tableElement, rowSelector, columnSelector) {
return ableElement.all(rowSelector).count().then(function(c) {
return ableElement.all(columnSelector).count().then(function (c2) {
return {
row: c,
column: c2
}
})
})
};
tableActions.getTableSize(table, by.css("tr"), by.css("th")).then(function (obj) {
console.log(obj);
})
The reason your code is failing is because you are using .then() on a function that does not return a promise.
Here's an example of a working promise:
let promise1 = new Promise( (resolve, reject) => {
let dataReceivedSuccessfully = false;
if (dataReceivedSuccessfully) {
resolve('Data Available!');
}
if (!dataReceivedSuccessfully) {
reject('Data Corrupted!');
}
})
promise1.then( (success) => {
console.log(success);
}).catch( (err) => {
console.log(err);
})
You can use this in your code to return a resolve or reject, and then you will be able to use .then().
https://medium.freecodecamp.org/promises-in-javascript-explained-277b98850de

Iterating file directory with promises and recursion

I know I'm returning early in the following function, how can I chain the recursive promises to my result?
My goal is to get an array of list of files in the directory and all of it's subdirectories. Array is single dimension, I'm using concat in this example.
function iterate(body) {
return new Promise(function(resolve, reject){
var list = [];
fs.readdir(body.path, function(error, list){
list.forEach(function(file){
file = path.resolve(body.path, file);
fs.stat(file, function(error, stat){
console.log(file, stat.isDirectory());
if(stat.isDirectory()) {
return iterate({path: file})
.then(function(result){
list.concat(result);
})
.catch(reject);
} else {
list.push(file);
}
})
});
resolve(list);
});
});
};
There are numerous mistakes in your code. A partial list:
.concat() returns a new array, so list.concat(result) by itself doesn't actually do anything.
You're calling resolve() synchronously and not waiting for all async operations to be completed.
You're trying to recursively return from deep inside several nested async callbacks. You can't do that. That won't get the results back anywhere.
I find this a ton easier to use by using a promisified version of the fs module. I use Bluebird to create that and then you can do this:
const path = require('path');
var Promise = require('bluebird');
const fs = Promise.promisifyAll(require('fs'));
function iterate(dir) {
return fs.readdirAsync(dir).map(function(file) {
file = path.resolve(dir, file);
return fs.statAsync(file).then(function(stat) {
if (stat.isDirectory()) {
return iterate(file);
} else {
return file;
}
})
}).then(function(results) {
// flatten the array of arrays
return Array.prototype.concat.apply([], results);
});
}
Note: I changed iterate() to just take the initial path so it's more generically useful. You can just pass body.path to it initially to adapt.
Here's a version using generic ES6 promises:
const path = require('path');
const fs = require('fs');
fs.readdirAsync = function(dir) {
return new Promise(function(resolve, reject) {
fs.readdir(dir, function(err, list) {
if (err) {
reject(err);
} else {
resolve(list);
}
});
});
}
fs.statAsync = function(file) {
return new Promise(function(resolve, reject) {
fs.stat(file, function(err, stat) {
if (err) {
reject(err);
} else {
resolve(stat);
}
});
});
}
function iterate2(dir) {
return fs.readdirAsync(dir).then(function(list) {
return Promise.all(list.map(function(file) {
file = path.resolve(dir, file);
return fs.statAsync(file).then(function(stat) {
if (stat.isDirectory()) {
return iterate2(file);
} else {
return file;
}
});
}));
}).then(function(results) {
// flatten the array of arrays
return Array.prototype.concat.apply([], results);
});
}
iterate2(".").then(function(results) {
console.log(results);
});
Here's a version that adds a customizable filter function:
function iterate2(dir, filterFn) {
// default filter function accepts all files
filterFn = filterFn || function() {return true;}
return fs.readdirAsync(dir).then(function(list) {
return Promise.all(list.map(function(file) {
file = path.resolve(dir, file);
return fs.statAsync(file).then(function(stat) {
if (stat.isDirectory()) {
return iterate2(file, filterFn);
} else {
return filterFn(file)? file : "";
}
});
})).then(function(results) {
return results.filter(function(f) {
return !!f;
});
});
}).then(function(results) {
// flatten the array of arrays
return Array.prototype.concat.apply([], results);
});
}
// example usage
iterate2(".", function(f) {
// filter out
return !(/(^|\/)\.[^\/\.]/g).test(f);
}).then(function(results) {
console.log(results);
});

Promise.promisify is not a function

I wrote JavaScript like this:
var keys=null;
var promise=Promise.promisify(alchemyapi.keywords("url",myUrl,{},function(response) {
var keywords = { url:myUrl, response:JSON.stringify(response,null,4), results:response['keywords'] };
return keywords;
}));
promise.then(
(result)=>{
var keys=result;
console.log(keys);
},
(error)=>console.log(error)
);
I'm using AlchemyAPI and trying to store data I got into my database
How should I do?
You should be able to use Promise to return expected results by removing .promisify which is not a built-in Promise method ; substituting passing keywords to resolve within Promise constructor for return
var keys = null
, promise = new Promise(function(resolve) {
alchemyapi.keywords("url", myUrl, {}, function(response) {
var keywords = {url: myUrl
, response: JSON.stringify(response,null,4)
, results:response['keywords']
};
resolve(keywords);
// error handling ?
})
}).then(function(result) {
keys = result;
console.log(keys)
}, function(err) {
console.log(err)
})
For a more general Promise.promisify function without Bluebird, I ended up writing this:
function promisify(func) {
return function promiseFunc(options) {
return new Promise(function executor(resolve, reject) {
func(options, function cb(err, val) {
if (err) {
return reject(err);
} else {
return resolve(val);
}
});
});
}
}
Hopefully someone else finds this helpful, but in most cases it's probably worth importing Bluebird.

All functions in Q.all block are not being promised

I have the following code below in a then block
The issue I'm facing is at the end when i do the res.json(optionData1) its not returning the fully completed js data object i.e. the output after the processData function is missing
Am i using Q.all in the correct way?
var processUserInfo = function(categoryToProcess, inputToProcess, optionComingIn) {
var d = Q.defer();
if (optionData1['option'] == optionComingIn) {
if (optionData1[categoryToProcess].hasOwnProperty(inputToProcess)) {
optionData1[categoryToProcess][inputToProcess]++;
} else {
optionData1[categoryToProcess][inputToProcess] = 1;
}
d.resolve(optionData1);
}
}
var processData = function(item, optionComingIn) {
var d = Q.defer();
return User.find(
{_id: item},
{gender: 1, country:1},
function(req, foundUser) {
processUserInfo('gender', foundUser[0]['gender'], optionComingIn)
.then(function(resolve,reject) {
d.resolve();
});
});
return d.promise;
}
Q.all(foundQ[0]['people'].map(function(item) { // Or Q.allSettled
processCounts(item['optionSelected']);
processData(item['userID'], item['optionSelected']);
}))
.then(function(){
res.json(optionData1); //Doesnt give me the full result
});
Thanks
UPDATE: Using the return method as in the answer below got everything working.
Here is code which may work - too much "unknown" in your code snippet to be sure
modified processData to return a promise that resolves when user.Find is done
added a return in the .map, so the promise returned by processData is waited on in Q.all
So ... here's the fixed code (processuserInfo unchanged so omitted form the answer)
var processData = function (item, optionComingIn) {
// return a promise to wait for
return Q.promise(function(resolve, reject) {
User.find({
_id: item
}, {
gender: 1,
country: 1
},
function (req, foundUser) {
processUserInfo('gender', foundUser[0]['gender'], optionComingIn);
resolve();
}
);
});
}
Q.all(foundQ[0]['people'].map(function (item) { // Or Q.allSettled
processCounts(item['optionSelected']);
return processData(item['userID'], item['optionSelected']);
// return added
}))
.then(function () {
res.json(optionData1); //Doesnt give me the full result
});

Categories