Daedalus Daedalus - 1 month ago 14
Javascript Question

Promises inside a setInterval

I've got a

setInterval
that runs a promise every second, inside the then function of every promise, I put the output into a MongoDB database (although for some reason it's not working).

I want to close the connection to the database when everything is done, but I don't know how to to make the closing of the connection only run when all promises are done and everything has finished writing to the DB.

Here is my current code:

I've got a
client.js
file, meant to issue the query to a shop, using a promise, and a
db.js
, meant to handle DB functionality.

client.js



var id = setInterval(function(){

if (i == (categories.length-1))
clearInterval(id);


var category = categories[i];

client.itemSearch({
searchIndex: SearchIndex,
categoryID: category.id,
keywords: currentKeyword
})
.then(function(results){

var title = results[0].Title;
var cat = category.name;
var price = results[0].Price

db.insertProduct(title,cat,price).then(function(){
console.log("Added " + title);
})
},function(err) {
console.log("error at " + category.name);
});
i+=1;
}, 1000)

queryStore();


db.js



var mongoose = require("mongoose");

mongoose.connect('mongodb://localhost:27017/catalog');

var schema = new mongoose.Schema({
title : String,
category : String,
price : Number,
}, {collection: "catalog"});

var Product = mongoose.model("Product", schema);

Product.remove({}, function() {
console.log('Database cleared.')
});


exports.clearDB = function() {
Product.remove({});
}

exports.insertProduct = function (Title,Category,Price) {
var entry = new Product({
title: Title,
category: Category,
price: Price,
});

entry.save();
}

exports.closeConn = function() {
console.log("Closing DB Connection");
mongoose.connect().close();
}


Also, since I'm absolutely new to JavaScript and Node.js in general, any best practices or general tips will be greatly appreciated! :)

Answer

As written you are relying on a 1 second interval to impose a delay between successive calls of a search/insert sequence. There's nothing fundamentally wrong with that but it doesn't guarantee that each step is complete before the next one starts, and doesn't guarantee that the next step starts as soon as possible. At each step, the 1 second delay may be more than adequate or less than adequate, you don't really know.

Fortunately, promises offer a much better way to cope with the asynchronism.

Starting with an array, a well tried reduce pattern is available (see "The Collection Kerfuffle" here and a more general treatment here) to impose a sequence :

array.reduce(function(promise, item) {
    return promise.then(function() {
        return doSomethingAsync(item);
    });
}, Promise.resolve());

where Promise is ES6's native Promise, or eg Bluebird.

For the code in the question, the doSomethingAsync() part expands to give :

categories.reduce(function(promise, category) {
    return promise.then(function() {
        return client.itemSearch({
            'searchIndex': SearchIndex,
            'categoryID': category.id,
            'keywords': currentKeyword
        }).then(function(results) {
            var title = results[0].Title;
            var cat = category.name;
            var price = results[0].Price;
            return db.insertProduct(title, cat, price);
        }).then(function() {
            console.log("Added " + title);
        }).catch(function(err) {
            console.log("error at " + category.name);
        });
    });
}, Promise.resolve());

The entire reduction process returns a promise, which can itself be returned and/or aggregated with other promises.

Comments