How to apply asynchronous fix to loops? - javascript

I'm stuck on some code in an index.js file that get the home page (called index.hbs), which references to a file called product-seeder.js (containing a loop that goes through all products listed in an array) and every time the loop goes through a listed product, it renders an output on the home page.
The server rendered way more outputs than I had listed products. I know that this is because javascript is asynchronous, but when I used the code from the tutorial(that is supposed to fix the asynchronous problem) and tried to load the server, it just kept loading and won't stop.
I have no clue to why it's doing that. Did I do something wrong? Thanks, help is much appreciated.
My index.js:
var express = require('express');
var router = express.Router();
var Product = require('../models/product');
/* GET home page. */
router.get('/', function(req, res, next) {
Product.find(function(err, docs) {
res.render('shop/index', {title: 'Shopping Cart', products: docs});
});
});
module.exports = router;
My product-seeder.js:
var Product = require('../models/product');
var mongoose = require('mongoose');
mongoose.connect('mongodb://localhost:27017/shopping', { useNewUrlParser: true });
var products = [
new Product({
imagePath: 'image.png',
title: 'PD 1',
description: 'Product 1',
price: 1
}),
new Product({
imagePath: 'image.png',
title: 'PD 2',
description: 'Product 2',
price: 2
}),
new Product({
imagePath: 'image.png',
title: 'PD 3',
description: 'Product 3',
price: 3
})
];
var done = 0;
for (var i = 0; i < products.length; i++) {
products[i].save(function() {
done++;
if (done === products.length) {
exit();
}
});
}
function exit() {
mongoose.disconnect();
}
My index.hbs:
{{# each products }}
Output text
{{/each}}

The main problem is that in the rendered page there are more products than expected.
On your database, how many products are saved? The same amount of the rendered? If yes, the issue happens when you save your products.
You can rewrite the "save logic" as following
const products = [/* .. things here .. */]
// This save one after another
async function save() {
for (var i = 0; i < products.length; i++) {
await products[i].save(); // this can generate an eslint warning/error
}
}
// This save all products basically together, if products.length is > 10 can be a bad idea
async function save() {
return Promise.all(products.map(prod => prod.save()));
}
save().then(() => mongoose.disconnect());

Related

jade iteration through dataset object from mongodb database

i'm building a bookstore app with expressjs and using mongodb to store data i have a database of books i want to display to my index.jade but i keep running into an infinite loop
extends layout
block content
.content
each book in books
.book
img(src="/images/dumbk.png", width="150px", height="200px")
h4.book-title #{book.title}
h6.book-dtls Price: #{book.price} SDG
p.book-desc #{book.description}
a.btn.btn-primary.view(href="/books/details/1") View Book
and this is my index.js
var express = require('express');
var router = express.Router();
var Book = require('../models/bookModel');
/* GET home page. */
router.get('/', /*ensureAuthenticated, */function(req, res, next) {
Book.find({}, function (err, books) {
if (err) {console.log(err);}
var model = {books: books}
console.log(model);
res.render('index', /*{books: books},*/ { title: 'Members' });
});
});
after logging model it logged all the books in my database but it does not render them and keeps running an infinite loop when i tried to log a book title by using book.title in the jade file
-console.log(book.title)
result was undefined
so am sure that the error is in the loop and the formatting
when i log the model the output is
[ { _id: the id, title: "book title", author: "author", price: "10", description: "a book description", stock: "5", cover: "dumbk.png" } ]
You cannot use console.log in the jade file because this is supposed to be a client side script. Your "books" object is back at the server, your console.log will be executed later when the client does it. Try this instead (although there are better ways):
extends layout
block content
each book in books
.book
img(src="/images/dumbk.png", width="150px", height="200px")
h4.book-title #{book.title}
h6.book-dtls Price: #{book.price} SDG
p.book-desc #{book.description}
a.btn.btn-primary.view(href="/books/details/1") View Book
script.
const books = JSON.parse("!{JSON.stringify(books)}");
console.log(books[0] && books[0].title);
Update: according to your comments, you didn't even want this, just to get your Jade file to render books. To do that, you should pass the books array to renderer options:
res.render('index', { title: 'Members', books });

How to search all keys inside MongoDB collection using only one keyword

Is there a way for MongoDB to search an entire collection's keys' contents using only a single search keyword?
Suppose I have the following collection (let's call it foodCollection):
{
name: "Chocolate Mousse Cake",
type: "Cake"
},
{
name: "Mother's Cookies",
type: "Cookies"
},
{
name: "Dark Bar",
type: "Chocolate"
}
I want my search to look for matches that contain "Chocolate", meaning it should return "Chocolate Mousse Cake" and "Dark Bar".
I'm trying to do this using the ff: code:
Client-side controller
// Search Products
$scope.searchProduct = function () {
$http.get('/api/products/search/' + $scope.searchKeyword).success(function(data){
console.log(data);
})
.error(function(err) {
console.log("Search error: " + err);
});
}
Express.js
app.get('/api/products/search/:param', productController.search); // Search for product
Server-side controller (I used this reference from the MongoDB docs):
// Search
module.exports.search = function(req, res) {
console.log("node search: " + req.body);
Product.find({ $or: [{productName: req.body},
{productType: req.body}]
}, function(err, results) {
res.json(results);
});
}
When I executed this, I got nothing. Am I missing something?
Any help would be greatly appreciated. Thank you.
UPDATE (FINAL)
Finally solved this thanks to Joydip's and digit's tips. Here's my solution in case somebody else gets the same problem as I did:
Client-side controller
$scope.searchProduct = function () {
if ($scope.searchKeyword == '') {
loadFromMongoDB(); // reloads original list if keyword is blank
}
else {
$http.get('/api/products/search/' + $scope.searchKeyword).success(function(data){
if (data.length === 0) {
$scope.showNoRec = true; // my flag that triggers "No record found" message in UI
}
else {
$scope.showNoRec = false;
$scope.productList = data; // passes JSON search results to UI
}
});
}
}
Express.js
app.get('/api/products/search/:keyword', productController.search); // Search for product
Mongoose schema
var mongoose = require('mongoose');
var schema = new mongoose.Schema({
productName: String,
productType: String,
productMaker: String,
productPrice: Number,
createDate: Date,
updateDate: Date
});
schema.index({productName: "text", productType: "text", productMaker: "text"});
Server-side controller
module.exports.search = function(req, res) {
Product.find({$text: {$search : req.params.keyword}}, function(err, results){
res.json(results);
})
}
Thank you everyone for your help. :)
You can try by creating an Index:
db.yourollection.createIndex({"productName":1,"productType":1})
And then by searching for the value, Example:
Product.find({$text:{$search: 'Chocolate'}},{productName:1, productType:1});
If you want to search all key, then you can use
db.foodCollection.createIndex( { name: "text", description: "text" } )
then search by
db.foodCollection.find({ $text: { $search: "choco" } })

Insert to db from Meteor server not working

I'm trying to run a simple script that will scrape some data using x-ray and insert it into my Events collection.
if (Meteor.isServer) {
var Xray = Meteor.npmRequire('x-ray');
var xray = new Xray({
version: "2.0.3"
});
xray('http://www.events12.com/seattle/january/', '.qq', [{
title: '.title',
date: '.date',
link: 'a #href',
allContent: '#html'
}])(function(err, content) {
for (var i = 0; i < content.length; i++) {
(function() {
console.log(i);
var newEvent = {
owner: 'me',
name: content[i].title,
date: content[i].date,
url: content[i].link,
createdAt: new Date(),
description: 'none'
};
console.log(newEvent);
Events.insert(newEvent, function(err, data) {
console.log(err);
console.log(data);
});
})();
}
});
}
The callback from x-ray that takes in content has all the scraped data in an array of objects, each with several properties. When I try to insert this data into my Events collection, the for loop iterates once and then exits, but no error is shown. If I remove the Events.insert() the loop iterates all the way through.
What am I missing? What is the proper way to execute such a task?
The Events.insert() was being called outside of any Meteor fibers. Adding Meteor.bindEnvironment() and feeding the entire function in as a callback fixed this problem.

NodeJS x-ray web-scraper: how to follow links and get content from sub page

So I am trying to scrape some content with node.js x-ray scraping framework. While I can get the content from a single page I can't get my head around on how to follow links and get content from a subpage in one go.
There is a sample on x-ray github profile but it returns empty data if I change the code to some other site.
I have simplified my code and made it crawl the SO questions for this sample.
The following works fine:
var Xray = require('x-ray');
var x = Xray();
x('http://stackoverflow.com/questions/9202531/minimizing-nexpectation-for-a-custom-distribution-in-mathematica', '#content', [{
title: '#question-header h1',
question: '.question .post-text'
}])
(function(err, obj) {
console.log(err);
console.log(obj);
})
This also works:
var Xray = require('x-ray');
var x = Xray();
x('http://stackoverflow.com/questions', '#questions .question-summary .summary', [{
title: 'h3',
question: x('h3 a#href', '#content .question .post-text'),
}])
(function(err, obj) {
console.log(err);
console.log(obj);
})
but this gives me empty details result and I can't figure out what is wrong:
var Xray = require('x-ray');
var x = Xray();
x('http://stackoverflow.com/questions', '#questions .question-summary .summary', [{
title: 'h3',
link: 'h3 a#href',
details: x('h3 a#href', '#content', [{
title: 'h1',
question: '.question .post-text',
}])
}])
(function(err, obj) {
console.log(err);
console.log(obj);
})
I would like my spider to crawl the page with listed questions and then follow the link to each question and retrieve additional information.
So with with some help I figured out what the problem was. I am posting this answer in case somebody else might have the same problem.
Working example:
var Xray = require('x-ray');
var x = Xray();
x('http://stackoverflow.com/questions', '#questions .question-summary .summary', [{
title: 'h3',
link: 'h3 a#href',
details: x('h3 a#href', {
title: 'h1',
question: '.question .post-text',
})
}])
(function(err, obj) {
console.log(err);
console.log(obj);
})
version 2.0.2 does work.. there is a current issue in github here to followhttps://github.com/lapwinglabs/x-ray/issues/189

Node.js: Creating multiple MongoDB docs over iterated JSON POST data

A webpage sends JSON data via POST to my Node.js App (MEAN-environment using Mongoose). The JSON file looks like this (excerpt):
Firstname: 'XY',
Surname: 'asd',
Articles:
[ { title: '1', description: 'XY' },
{ title: '2', description: 'XY' },
{ title: '3', description: 'XY' }
The purpose is to create an Author in a mongodb database, add the author to an additional directory, store associated articles and create references to those articles in the author document (code excluded). This is the code that handles the request:
[...]
async.waterfall([
//Create random code for author (besides mongodb-specific id)
function(callback){
newAuthorCode.randomAuthorCode(function(err, code) {
callback(null, code);
});
},
//Save new author to db
function(code, callback){
var newAuthor = new Author({firstname: req.body.Firstname,
surname: req.body.Surname,
identcode: code
});
newAuthor.save(function (err){
callback(null, newAuthor);
});
},
//Add new author to an additional directory
function(newAuthor, callback){
Directory.update({_id: req.user._id}, {$push: {authorids: newAuthor._id }}, function(err, update){
if (update){
callback(null);
}
});
},
//saves articles to db
function(callback){
var keys = Object.keys(req.body.articles);
for(var i=0, length=keys.length; i<length; i++){
var newArticle = new Article({title: req.body.Articles[keys[i]].title,
description: req.body.Articles[keys[i]].description
});
newArticle.save(function (err){
console.log(newArticle._id); // <--- !!!!!!!
});
}
callback(null);
}
], function (err, result) {
console.log('DONE!!!');
res.send('200');
});
My problems:
1) The marked line of code where I try to output all IDs of the generated articles only delivers i-times the ID of the last article stored (in this case of article 3).
2) Problem 1 leads to the issue that I can not create references of the newly created articles in the author document (stored in a different collection) as I can't access no article IDs but the last one!?
3) Sometimes the author, as well as the articles, are created multiple times in the database (with huge time gap in between)!?
Thanks for any advice as I am running out of ideas.
Igor
Not sure what newLink is in your code, but you can try this instead:
newArticle.save(function (err, newDoc){
console.log(newDoc._id); // <--- !!!!!!!
});

Categories

Resources