Create an async function getUsers(names), that gets an array of GitHub logins, fetches the users from GitHub and returns an array of GitHub users.
The GitHub url with user information for the given USERNAME is: https://api.github.com/users/USERNAME.
There’s a test example in the sandbox.
Important details:
1.There should be one fetch request per user.
2.Requests shouldn’t wait for each other. So that the data arrives as soon as possible.
3.If any request fails, or if there’s no such user, the function should return null in the resulting array.
Input:array;
output:array;
TypeError: r.json is not a function
async function getUsers(names) {
let requests = names.map(name => fetch(`https://api.github.com/users/${name}`));//gets users
let users = [];//Final answer
await Promise.allSettled(requests)
.then(responses => new Promise(function(resolve) {// returrn correct users promise
let corrects = [];
responses.forEach((result) => {
if (result.value.ok) { //check statuse 200-299
corrects.push(result);
} else {
users.push(result); // else add to Finell answer null
}
})
resolve(corrects); //return users with 200-299 statuse
}))
.then(corrects => Promise.all(corrects.map(r => r.json()))) //processing
.then(results => results.forEach(result => users.push(result))); //add to finel answer correct requests
return users;
}
//Input:array;
//output:array;
//TypeError: r.json is not a function
There's a number of things slightly wrong with your code, but I think the main issue is that you're pushing the results of allSettled into 'corrects' but, you want to push the .value instead.
You also don't actually do anything with corrects and only return failed requests.
But here's a version that cleans it all up. I'm assuming you want to ignore failed requests, but not sure, because it's hard to tell from your code:
async function getUsers(names) {
const requests = names.map(name => fetch(`https://api.github.com/users/${name}`));//gets users
const results = await Promise.allSettled(requests);
const successResponses = results
.filter(result => {
// Filter out rejected promises and error responses.
// I think this is what you want but not sure?
if (result.status!=='fulfilled' || !result.value.ok) return false;
});
return Promise.all(successResponses.map(response => response.json()));
}
Promise.allSettled is a very special-purpose function and you will not need it in most cases. There are other pain points like the explicit promise constructor anti-pattern. Instead decompose the problem into smaller, simple parts -
getUser(name) takes a single name and returns a user object or null
getUsers(names) takes a list of names and maps getUser over each
async function getUser(name) {
try {
const res = await fetch(`https://api.github.com/users/${name}`)
return res.ok ? res.json() : null
}
catch (err) {
return null
}
}
function getUsers(names) {
return Promise.all(names.map(getUser))
}
getUsers(["ivg", "glennsl", "jeffsco", "nosuchuser111"]).then(console.log, console.error)
.as-console-wrapper { min-height: 100%; top: 0; }
[
{
"login": "ivg",
"id": 2336698,
"node_id": "MDQ6VXNlcjIzMzY2OTg=",
...
},
{
"login": "glennsl",
"id": 5207036,
"node_id": "MDQ6VXNlcjUyMDcwMzY=",
...
},
{
"login": "jeffsco",
"id": 4043178,
"node_id": "MDQ6VXNlcjQwNDMxNzg=",
...
},
null // user not found
]
After executing this query:
con.query("SELECT name FROM members WHERE id=1", function(err, rows, fields) {
console.log(rows);
});
this is what I get:
[ RowDataPacket { name: 'John' } ]
I expect only one record and I only want to get John. How do I do that?
Short of writing your own additional function(s) and passing the row object through them your desired result format is not possible based on the design of the mysqljs/mysql package. In a standard SELECT query the result is always an object.
There is a bit of a hack workaround by using the stream function. Below is an example, but you will still end up with more lines of code than you would if you simply accessed the value via rows[0].name in a standard query.
let the_query = con.query('SELECT name FROM members WHERE id = 1;');
the_query
.on('result', function(row) {
console.log('row.name:', row.name);
// expected in log:
// row.name: John
})
.on('end', function() {
// this chained event can be excluded
// here for demo purposes
console.log('all done');
});
JSON.stringify(RowDataPacket[0].name)
This will output:
John
I parse an json object and for each element, i need to execute many queries.
In first, a "select" query and depending on the result, i execute an insert or an update.
I would like use async.js and knex.js
The issue it's the order of execution is not the searched order
async.each(newContent,function(e){
//var e=JSON.stringify(element),
var z=-1,
devicepresenceId = e.device_presence_id;
//console.log(e);
async.waterfall([
function(cb) {
knex('jos_joomrh_event_employee_hours_presence')
.whereRaw('device_presence_id=?', devicepresenceId)
.select('id', 'applied_at', 'applied_at_end')
.debug()
.then(function (rows) {
console.log(rows);
z = _.keys(rows).length;
console.log('rows0', z);
cb(null,z);
})
.catch(function (e) {
console.log(e)
reject(e)
})
cb(null,z);
},
function(z,cb){
console.log('z',z);
if (parseInt(z)==0)
{
console.log('insertHoursPresence');
//insertHoursPresence(e)
}
else{
console.log('updateHoursPresence');
//updateHoursPresence(e)
}
cb(null,'two')
}
],
function(err,z){
if(err)console.log(err);
console.log(z);
}
)}
)}
In fact; it executed the second function and and the cb function and after the first function with knex.:
Thanks for your help
Mdouke
In the knex part, you have a call to "cb(null,z)", you have to "move" that call inside catch function (but replacing reject(e) part). Your problem is that you're calling cb() function outside knex, of course it is called immediately independent of knex result)
My goal is to write the Fields from the JSON object below into a MySQL Database.
var dataRow = {
Table: "promosbudget",
Fields:[{
ManagerID: "Jose",
UserID: "ife",
Budget: "50000",
Year: "2015"
},
{
ManagerID: "Jose",
UserID: "fgs",
Budget: "50000",
Year: "2015"
},
{
ManagerID : "Jose",
UserID : "brz",
Budget : "50000",
Year : "2015"
}]
};
I'm using this command to receive and write the data:
app.post('/paramsjson', jsonParser, function(req, res) {
conMySQL.query('INSERT INTO ' + req.body.Table + ' SET ?',req.body.Fields,
function(err,result) {
console.log(result);
}
);
});
The issue is that I can only write the first JSON row, the other 2 rows are omitted.
I'd like to ask if there is a recommended method to do that right when I need to export a large JSON object (100.000 row), is it necessary to make a loop and read each row sequentially?
Thanks in advance for your help!
Try to go step by step.
Don't INSERT right into the database,
first console.log the outputs, look into the results, try to INSERT directly from the code. Then try to combine it all.
Anyway - I highly recommend using knex for any DB operation.
Take this sample codes for testing:
app.post('/paramsjson', jsonParser, function(req, res) {
console.log(req.body);
var table = req.body.Table;
var fields = req.body.Fields;
//If you want to use knex:
knex( table ).insert( fields )
.then(function (result) {
console.log(result)
})
.catch(function (err) {
console.log(err)
})
});
I've got myself a question regarding associations in Sails.js version 0.10-rc5. I've been building an app in which multiple models are associated to one another, and I've arrived at a point where I need to get to nest associations somehow.
There's three parts:
First there's something like a blog post, that's being written by a user. In the blog post I want to show the associated user's information like their username. Now, everything works fine here. Until the next step: I'm trying to show comments which are associated with the post.
The comments are a separate Model, called Comment. Each of which also has an author (user) associated with it. I can easily show a list of the Comments, although when I want to display the User's information associated with the comment, I can't figure out how to populate the Comment with the user's information.
In my controller i'm trying to do something like this:
Post
.findOne(req.param('id'))
.populate('user')
.populate('comments') // I want to populate this comment with .populate('user') or something
.exec(function(err, post) {
// Handle errors & render view etc.
});
In my Post's 'show' action i'm trying to retrieve the information like this (simplified):
<ul>
<%- _.each(post.comments, function(comment) { %>
<li>
<%= comment.user.name %>
<%= comment.description %>
</li>
<% }); %>
</ul>
The comment.user.name will be undefined though. If I try to just access the 'user' property, like comment.user, it'll show it's ID. Which tells me it's not automatically populating the user's information to the comment when I associate the comment with another model.
Anyone any ideals to solve this properly :)?
Thanks in advance!
P.S.
For clarification, this is how i've basically set up the associations in different models:
// User.js
posts: {
collection: 'post'
},
hours: {
collection: 'hour'
},
comments: {
collection: 'comment'
}
// Post.js
user: {
model: 'user'
},
comments: {
collection: 'comment',
via: 'post'
}
// Comment.js
user: {
model: 'user'
},
post: {
model: 'post'
}
Or you can use the built-in Blue Bird Promise feature to make it. (Working on Sails#v0.10.5)
See the codes below:
var _ = require('lodash');
...
Post
.findOne(req.param('id'))
.populate('user')
.populate('comments')
.then(function(post) {
var commentUsers = User.find({
id: _.pluck(post.comments, 'user')
//_.pluck: Retrieves the value of a 'user' property from all elements in the post.comments collection.
})
.then(function(commentUsers) {
return commentUsers;
});
return [post, commentUsers];
})
.spread(function(post, commentUsers) {
commentUsers = _.indexBy(commentUsers, 'id');
//_.indexBy: Creates an object composed of keys generated from the results of running each element of the collection through the given callback. The corresponding value of each key is the last element responsible for generating the key
post.comments = _.map(post.comments, function(comment) {
comment.user = commentUsers[comment.user];
return comment;
});
res.json(post);
})
.catch(function(err) {
return res.serverError(err);
});
Some explanation:
I'm using the Lo-Dash to deal with the arrays. For more details, please refer to the Official Doc
Notice the return values inside the first "then" function, those objects "[post, commentUsers]" inside the array are also "promise" objects. Which means that they didn't contain the value data when they first been executed, until they got the value. So that "spread" function will wait the acture value come and continue doing the rest stuffs.
At the moment, there's no built in way to populate nested associations. Your best bet is to use async to do a mapping:
async.auto({
// First get the post
post: function(cb) {
Post
.findOne(req.param('id'))
.populate('user')
.populate('comments')
.exec(cb);
},
// Then all of the comment users, using an "in" query by
// setting "id" criteria to an array of user IDs
commentUsers: ['post', function(cb, results) {
User.find({id: _.pluck(results.post.comments, 'user')}).exec(cb);
}],
// Map the comment users to their comments
map: ['commentUsers', function(cb, results) {
// Index comment users by ID
var commentUsers = _.indexBy(results.commentUsers, 'id');
// Get a plain object version of post & comments
var post = results.post.toObject();
// Map users onto comments
post.comments = post.comments.map(function(comment) {
comment.user = commentUsers[comment.user];
return comment;
});
return cb(null, post);
}]
},
// After all the async magic is finished, return the mapped result
// (or an error if any occurred during the async block)
function finish(err, results) {
if (err) {return res.serverError(err);}
return res.json(results.map);
}
);
It's not as pretty as nested population (which is in the works, but probably not for v0.10), but on the bright side it's actually fairly efficient.
I created an NPM module for this called nested-pop. You can find it at the link below.
https://www.npmjs.com/package/nested-pop
Use it in the following way.
var nestedPop = require('nested-pop');
User.find()
.populate('dogs')
.then(function(users) {
return nestedPop(users, {
dogs: [
'breed'
]
}).then(function(users) {
return users
}).catch(function(err) {
throw err;
});
}).catch(function(err) {
throw err;
);
Worth saying there's a pull request to add nested population: https://github.com/balderdashy/waterline/pull/1052
Pull request isn't merged at the moment but you can use it installing one directly with
npm i Atlantis-Software/waterline#deepPopulate
With it you can do something like .populate('user.comments ...)'.
sails v0.11 doesn't support _.pluck and _.indexBy use sails.util.pluck and sails.util.indexBy instead.
async.auto({
// First get the post
post: function(cb) {
Post
.findOne(req.param('id'))
.populate('user')
.populate('comments')
.exec(cb);
},
// Then all of the comment users, using an "in" query by
// setting "id" criteria to an array of user IDs
commentUsers: ['post', function(cb, results) {
User.find({id:sails.util.pluck(results.post.comments, 'user')}).exec(cb);
}],
// Map the comment users to their comments
map: ['commentUsers', function(cb, results) {
// Index comment users by ID
var commentUsers = sails.util.indexBy(results.commentUsers, 'id');
// Get a plain object version of post & comments
var post = results.post.toObject();
// Map users onto comments
post.comments = post.comments.map(function(comment) {
comment.user = commentUsers[comment.user];
return comment;
});
return cb(null, post);
}]
},
// After all the async magic is finished, return the mapped result
// (or an error if any occurred during the async block)
function finish(err, results) {
if (err) {return res.serverError(err);}
return res.json(results.map);
}
);
You could use async library which is very clean and simple to understand. For each comment related to a post you can populate many fields as you want with dedicated tasks, execute them in parallel and retrieve the results when all tasks are done. Finally, you only have to return the final result.
Post
.findOne(req.param('id'))
.populate('user')
.populate('comments') // I want to populate this comment with .populate('user') or something
.exec(function (err, post) {
// populate each post in parallel
async.each(post.comments, function (comment, callback) {
// you can populate many elements or only one...
var populateTasks = {
user: function (cb) {
User.findOne({ id: comment.user })
.exec(function (err, result) {
cb(err, result);
});
}
}
async.parallel(populateTasks, function (err, resultSet) {
if (err) { return next(err); }
post.comments = resultSet.user;
// finish
callback();
});
}, function (err) {// final callback
if (err) { return next(err); }
return res.json(post);
});
});
As of sailsjs 1.0 the "deep populate" pull request is still open, but the following async function solution looks elegant enough IMO:
const post = await Post
.findOne({ id: req.param('id') })
.populate('user')
.populate('comments');
if (post && post.comments.length > 0) {
const ids = post.comments.map(comment => comment.id);
post.comments = await Comment
.find({ id: commentId })
.populate('user');
}
Granted this is an old question, but a much simpler solution would be to loop over the comments,replacing each comment's 'user' property (which is an id) with the user's full detail using async await.
async function getPost(postId){
let post = await Post.findOne(postId).populate('user').populate('comments');
for(let comment of post.comments){
comment.user = await User.findOne({id:comment.user});
}
return post;
}
Hope this helps!
In case anyone is looking to do the same but for multiple posts, here's one
way of doing it:
find all user IDs in posts
query all users in 1 go from DB
update posts with those users
Given that same user can write multiple comments, we're making sure we're reusing those objects. Also we're only making 1 additional query (whereas if we'd do it for each post separately, that would be multiple queries).
await Post.find()
.populate('comments')
.then(async (posts) => {
// Collect all comment user IDs
const userIDs = posts.reduce((acc, curr) => {
for (const comment of post.comments) {
acc.add(comment.user);
}
return acc;
}, new Set());
// Get users
const users = await User.find({ id: Array.from(userIDs) });
const usersMap = users.reduce((acc, curr) => {
acc[curr.id] = curr;
return acc;
}, {});
// Assign users to comments
for (const post of posts) {
for (const comment of post.comments) {
if (comment.user) {
const userID = comment.user;
comment.user = usersMap[userID];
}
}
}
return posts;
});