Promise.map returning a broken object - javascript

i'm using the Promise.map to make some operations in an array of objects, which object represents a file.
Inside i'm mapping this files, and when i finish, i want return a object just like this:
{
output: {
order_by: 'Competência',
output_data: 'TEST.dados.csv',
output_result: 'TEST.resultado.csv'
},
files: [{
full_name: 'Relat¢rio RE (1) 02.2014.pdf',
mime: 'application/pdf',
pages: [],
status: [],
text: ''
}, {
exists: false,
extension: 'pdf',
full_name: 'Relat¢rio RE (1) 02.2014.pdf',
mime: 'application/pdf',
pages: [],
status: [],
text: ''
}]
}
And when i use my code like this:
new Promise(function(resolve, reject) {
return Promise.map(files, function(file) {
var map;
result.push(mapping_files(file));
if (result.length === files.length) {
map = {
output: mapping_output(output),
files: result
};
return resolve(map);
}
}, {
concurrency: 3000
});
});
With resolve, reject.. the output will be PERFECT.. but if i delete the new Promise:
return Promise.map(files, function(file) {
var map;
result.push(mapping_files(file));
if (result.length === files.length) {
return map = {
output: mapping_output(output),
files: result
};
}
}, {
concurrency: 3000
});
The output will be like this:
[undefined, {
output: {
order_by: 'Competência',
output_data: 'TEST.dados.csv',
output_result: 'TEST.resultado.csv'
},
files: [
[Object],
[Object]
]
}]
Why this? I MUST return an array when i'm using the Promise.map?
Thanks.

Related

How to extract paths to 'enabled' objects in nested object arrays

I'm a novice to recursion and I have a JSON structure with arrays of nested objects. Some of these objects have a boolean enabled: true. I'm trying to figure out how to extract the paths to all enabled objects and their children.
I tried both cleaning up the original object by removing unused paths but I got lost in accessing the parents. I also tried building a separate array of paths using dot-notation, as I can probably build a new nested object from that. My latest attempt at the dot-notation extract:
const sourceData = {
title: "Work",
tags: [
{
title: "Cleaning",
tags: [
{
title: "Floors"
},
{ title: "Windows", enabled: true },
{ title: "Ceilings", enabled: true }
]
},
{
title: "Maintenance",
tags: [
{
title: "Walls",
enabled: true,
tags: [
{
title: "Brickwall"
},
{
title: "Wooden wall"
}
]
},
{
title: "Roof"
}
]
},
{
title: "Gardening"
}
]
};
function getEnabledPaths(level, acc) {
for (const tag of level.tags) {
if (tag.enabled) {
return tag.title;
} else if (tag.hasOwnProperty("tags")) {
var path = this.getEnabledPaths(tag);
if (path) acc.push(tag.title + "." + path);
}
}
return acc;
}
console.log(getEnabledPaths(sourceData, []));
I only get:
[
"Cleaning.Windows",
"Maintenance.Walls"
]
I would ideally end up with something like this:
[
'Work.Cleaning.Windows',
'Work.Cleaning.Ceilings',
'Work.Maintenance.Walls.Brickwall',
'Work.Maintenance.Walls.Wooden Wall'
]
In a perfect world (but I tried for days and went back to getting the dot notation results):
{
title: "Work",
tags: [
{
title: "Cleaning",
tags: [
{
title: "Windows",
enabled: true
},
{
title: "Ceilings",
enabled: true
}
]
},
{
title: "Maintenance",
tags: [
{
title: "Walls",
enabled: true,
tags: [
{
title: "Brickwall"
},
{
title: "Wooden wall"
}
]
}
]
}
]
};
The key to the recursion function is to both a) deal with children and b) the item itself.
Here's my take, which seems to work:
const sourceData = {title:"Work",tags:[{title:"Cleaning",tags:[{title:"Floors"},{title:"Windows",enabled:true},{title:"Ceilings",enabled:true}]},{title:"Maintenance",tags:[{title:"Walls",enabled:true,tags:[{title:"Brickwall"},{title:"Woodenwall"}]},{title:"Roof"}]},{title:"Gardening"}]};
function itemFilter(item) {
// enabled? done with this item
if (item.enabled) return item;
// not enabled and no tags? set to null
if (!item.tags) return null;
// filter all children, remove null children
item.tags = item.tags.map(child => itemFilter(child)).filter(child => child);
return item;
}
console.log(itemFilter(sourceData));
.as-console-wrapper {
max-height: 100vh !important;
}
You could pass enabled parameter down to lower levels of recursion if true value is found on some of the upper levels and based on that add path to the results or not.
const data ={"title":"Work","tags":[{"title":"Cleaning","tags":[{"title":"Floors"},{"title":"Windows","enabled":true},{"title":"Ceilings","enabled":true}]},{"title":"Maintenance","tags":[{"title":"Walls","enabled":true,"tags":[{"title":"Brickwall"},{"title":"Wooden wall"}]},{"title":"Roof"}]},{"title":"Gardening"}]}
function paths(data, prev = '', enabled = false) {
const result = [];
prev += (prev ? "." : '') + data.title;
if (!enabled && data.enabled) enabled = true;
if (!data.tags) {
if (enabled) {
result.push(prev);
}
} else {
data.tags.forEach(el => result.push(...paths(el, prev, enabled)))
}
return result;
}
const result = paths(data)
console.log(result)

Mongoose $slice and get orginal size array

I'm currently trying to get the total amount of items in my News object, and return a slice of the items as objects.
I found out how to use the $slice operator in my query, but I don't know how to get the original size of the array of items.
The code I'm currently using in NodeJS:
if (req.query.limit) {
limit = 5;
}
News.findOne({ connected: club._id }, {items: {$slice: limit}}).exec(function (err, news) {
if (err) {
return res.status(400).send({
message: errorHandler.getErrorMessage(err)
});
} else if (!news || news.items.length === 0) {
res.jsonp([]);
} else {
const returnObj = { items: [], totalNumber: 0 };
const items = news.items.sort(function (a, b) {
return b.date - a.date
});
res.jsonp({
items: items,
totalNumber: news.items.length
});
}
});
The Mongo model:
var mongoose = require('mongoose'),
validator = require('validator'),
Schema = mongoose.Schema;
var NewsSchema = new Schema({
connected: {
type: Schema.Types.ObjectId,
required: 'Gelieve een club toe te wijzen.',
ref: 'Club'
},
items: [{
userFirstName: String,
action: String,
date: Date,
targetName: String
}],
created: {
type: Date,
default: Date.now
}
});
module.exports = mongoose.model('News', NewsSchema);
How would I do this efficiently?
Thanks!
EDIT: final code which works:
News.aggregate([{
$match: {
connected: club._id
}
}, {
$project: {
totalNumber: {
$size: '$items'
},
items: {
$slice: ['$items', limit]
}
}
}
]).exec(function (err, news) {
console.log(news);
if (!news || news[0].items.length === 0) {
res.jsonp([]);
} else {
res.jsonp(news[0]);
}
});
You cannot have both information at once using find and $slice.
The soluce you have :
Use aggregate to return the count and only the sliced values.
Like :
[{
$project: {
count: {
$size: "$params",
},
params: {
$slice: ["$params", 5],
},
},
}]
To help you out making aggregate, you can use the awesome mongodb-compass software and its aggregate utility tool.
Use a find without $slice, get the number of item there, and then slice in javascript the array before returning it.
EDIT :
[{
$sort: {
'items.date': -1,
},
}, {
$project: {
count: {
$size: "$items",
},
params: {
$slice: ["$items", 5],
},
},
}]

Loop through JS Object and function on each item

I must be over thinking the solution for this problem but can't seem to get this right.
I have an array object like so:
[
{ ItemID: 1, Path: '/Admin', Name: 'Admin' },
{ ItemID: 2, Path: '/Product', Name: 'Product' },
{ ItemID: 1, Path: '/Reports', Name: 'Reports' }
]
I want to map over each item and for each one I need to run a function that will return whether they have access. i.e. a boolean (yes/no).
So far I have something like this:
const newData = data.map((curr, val , arr) => {
if (checkAccess(username, curr.Name )) { //checkAccess returns true or false
return { ...current };
}
});
I only want to return the ones they have access to.
so assuming that a user is unable to access Admin the final object should be:
[
{ ItemID: 2, Path: '/Product', Name: 'Product' },
{ ItemID: 1, Path: '/Reports', Name: 'Reports' }
]
EDIT:
The issue is also that the function isn't returning a true / false
function checkInGroup(username, name) {
let inGroup = "";
ad.isUserMemberOf(username, name, function(err, isMember) {
if (err) {
return res.json("Error ", err);
}
inGroup = isMember; //this part returns true
});
return inGroup; //this seems to return empty string
}
try using filter, as it creates a new array with all elements that pass the condition:
const res = data.filter(obj => obj.Path !== '/Admin');
console.log(res);

Creating an array of custom objects from nested data structure

I'm working on a project that requires me to massage some API data (shown in the snippet below as 'apiData'). The data structure I ultimately need for the charting library I'm using (Recharts) is this:
[
{ date: '2018-04-24', TSLA: 283.37, AAPL: 250.01 },
{ date: '2018-04-25', AAPL: 320.34 }
]
I've put together the function below and it works well enough, but I'm having trouble getting all the data to show up, even if there's no match between dates. In the below example, you'll notice that the object for date "2018-04-23" in the apiData is excluded. Ideally, the final ds would look like this:
[
{ date: '2018-04-23', TSLA: 285.12 }
{ date: '2018-04-24', TSLA: 283.37, AAPL: 250.01 },
{ date: '2018-04-25', AAPL: 320.34 }
]
Also, there's probably a more performant way to do this, but I've been hacking away for a while and not seeing a better solution at the moment. E.g. the forEach isn't ideal as the data set grows, which it will when I need to plot long time periods of data.
So my questions are: 1) How can I make sure objects that match in date are combined while objects that don't are still included and 2) what's a more performant way to do this operation?
If anyone has any input or critique of my approach and how I can improve it, I'd greatly appreciate it.
Here's a link to a repl if it's more convenient then the code snippet below.
formatChartData = (data) => {
const chartData = data
.reduce((arr, stock) => {
const stockArr = stock.chart.map((item) => {
let chartObj = {};
chartObj.date = item.date;
chartObj[stock.quote.symbol] = item.close;
if (arr.length > 0) {
arr.forEach((arrItem) => {
if (arrItem.date === item.date) {
chartObj = { ...arrItem, ...chartObj };
}
});
}
return chartObj;
});
return stockArr;
}, []);
console.log(chartData)
}
const apiData = [
{
chart: [
{
date: "2018-04-23",
open: 291.29,
close: 285.12,
},
{
date: "2018-04-24",
open: 291.29,
close: 283.37,
},
],
news: [],
quote: {
symbol: "TSLA"
},
},
{
chart: [
{
date: "2018-04-24",
open: 200.29,
close: 250.01,
},
{
date: "2018-04-25",
open: 290.20,
close: 320.34,
},
],
news: [],
quote: {
symbol: "AAPL"
},
}
]
formatChartData(apiData)
EDIT: I ended up using charlietfl's solution with an inner forEach as I found this easier to read than using two reduce methods. The final function looks like this:
const chartData = data
.reduce((map, stock) => {
stock.chart.forEach((chart) => {
const chartObj = map.get(chart.date) || { date: chart.date };
chartObj[stock.quote.symbol] = chart.close;
map.set(chart.date, chartObj);
});
return map;
}, new Map());`
A cleaner way than having to loop through the accumulated new array each time to look for a date is to use one master object with dates as keys
Following I use reduce() to return a Map (could be an object literal also) using dates as keys and then convert the Map values iterable to array to get the final results
const dateMap = apiData.reduce((map,stock)=>{
return stock.chart.reduce((_, chartItem)=>{
// get stored object for this date, or create new object
const dayObj = map.get(chartItem.date) || {date: chartItem.date};
dayObj[stock.quote.symbol] = chartItem.close;
// store the object in map again using date as key
return map.set(chartItem.date, dayObj);
},map);
}, new Map)
const res = [...dateMap.values()];
console.log(res)
.as-console-wrapper {max-height: 100%!important;}
<script>
const apiData = [
{
chart: [
{
date: "2018-04-23",
open: 291.29,
close: 285.12,
},
{
date: "2018-04-24",
open: 291.29,
close: 283.37,
},
],
news: [],
quote: {
symbol: "TSLA"
},
},
{
chart: [
{
date: "2018-04-24",
open: 200.29,
close: 250.01,
},
{
date: "2018-04-25",
open: 290.20,
close: 320.34,
},
],
news: [],
quote: {
symbol: "AAPL"
},
}
]
</script>
Just correcting your code only, else reduce should be used instead of map, for charts also.
formatChartData = (data) => {
const chartData = data
.reduce((arr, stock) => {
const stockArr = stock.chart.map((item) => {
let chartObj = {};
chartObj.date = item.date;
chartObj[stock.quote.symbol] = item.close;
if (arr.length > 0) {
arr.forEach((arrItem, i) => {
if (arrItem.date === item.date) {
chartObj = { ...arrItem, ...chartObj };
delete(arr[i]);
}
});
}
return chartObj;
});
return [...arr, ...stockArr].filter(e=>!!e); //to remove undefined elements left by delete above.
}, []);
console.log(chartData)
}
const apiData = [
{
chart: [
{
date: "2018-04-23",
open: 291.29,
close: 285.12,
},
{
date: "2018-04-24",
open: 291.29,
close: 283.37,
},
],
news: [],
quote: {
symbol: "TSLA"
},
},
{
chart: [
{
date: "2018-04-24",
open: 200.29,
close: 250.01,
},
{
date: "2018-04-25",
open: 290.20,
close: 320.34,
},
],
news: [],
quote: {
symbol: "AAPL"
},
}
]
formatChartData(apiData)
The problem is that your reduce function is running for each item in the data array. When it runs on the first item in the data array it returns:
[
{ date: '2018-04-23', TSLA: 285.12 },
{ date: '2018-04-24', TSLA: 283.37 }
]
When it runs on the second item in the array it returns this:
[
{ date: '2018-04-24', TSLA: 283.37, AAPL: 250.01 },
{ date: '2018-04-25', AAPL: 320.34 }
]
This is because when the reduce runs on the last array item it is returning the result from that item. You are only merging items from the accumulator variable "arr" if their date is in the current array item as well. Since 2018-04-23 is in the first but not the second it is not being added. I have added two things to your code. First if the current date being looped on is in the accumulator variable "arr" I delete it from "arr" after it is merged in. The second change is after each .reduce loop there is still some dates left in "arr" that aren't in the current "stockArr". To deal with this I merge both "arr" and "stockArr" which will give you what you are looking for.
formatChartData = (data) => {
const chartData = data
.reduce((arr, stock) => {
const stockArr = stock.chart.map((item) => {
let chartObj = {};
chartObj.date = item.date;
chartObj[stock.quote.symbol] = item.close;
if (arr.length > 0) {
arr.forEach((arrItem, index) => {
if (arrItem.date === item.date) {
chartObj = { ...arrItem, ...chartObj };
arr.splice(index, 1);
}
});
}
return chartObj;
});
return [...arr, ...stockArr];
}, []);
console.log(chartData)
}
const data = [
{
chart: [
{
date: "2018-04-23",
open: 291.29,
close: 285.12,
},
{
date: "2018-04-24",
open: 291.29,
close: 283.37,
},
],
news: [],
quote: {
symbol: "TSLA"
},
},
{
chart: [
{
date: "2018-04-24",
open: 200.29,
close: 250.01,
},
{
date: "2018-04-25",
open: 290.20,
close: 320.34,
},
],
news: [],
quote: {
symbol: "AAPL"
},
}
]
formatChartData(data)

Determine when async execution is finished

I have to process an array of entries that requires to perform to async tasks for each file entry: getFile and uploadResult both are async task. My question is how can I know when the array doc.entries is being processed using an async library like asyncjs. The code below is just an illustration of what I am trying to accomplish.
var doc = {
version: '1.7',
entries: [{
date: '11/11/10',
files: [{
name: 100,
executable: false
},
{
name: 101,
executable: false
}]
},
{
date: '11/12/10',
files: [{
name: 200,
executable: false
},
{
name: 201,
executable: false
}]
},
{
date: '11/13/10',
files: [{
name: 300,
executable: false
}]
},
{
date: '11/14/10',
files: [{
name: 400,
executable: false
}]
}]
};
doc.entries.map(function(entry){
entry.files.map(function(file){
getFile(file, function(err, result){
if(err){
throw Error(err)
}
uploadResult(result, function(err, status){
WriteOnDb(file.name, status, function(err, result){ ... });
});
})
});
});
How can I know when the last file is being store on the db and then do something else?
Thanks.
The easiest way is to use promises, or better observables, but you do it with callbacks too - for example you can count how many tasks are in total and how many was finished:
var total = doc.entries
.map(function (entry) {
return entry.files.length;
})
.reduce(function (x, acc) {
return acc + x
}, 0);
var finished = 0;
function finishCallback(err) {
if (err === null) {
/// all async tasks are finished;
}
}
doc.entries.map(function (entry) {
entry.files.map(function (file) {
getFile(file, function (err, result) {
if (err) {
finishCallback(err);
} else {
uploadResult(result, function (err, status) {
WriteOnDb(file.name, status, function (err, result) {
if (err) {
finishCallback(err);
} else {
finished += 1;
if (finished === total) finishCallback(null);
}
});
});
}
})
});
});

Categories

Resources