Promise resolves before forEach loop completes - javascript

I'm building my first CRUD (library) application and recently learned about promises as a means to avoid deeply-nested callbacks. I'm attempting to seed my DB with some data each time the server starts, but I seem to be missing something conceptually.
I have four objects in a bookData array that I want to iterate over and save to the DB using Mongoose:
function seedBooks() {
return new Promise(function(resolve,reject){
bookData.forEach(function(seed){
Book.create(seed, function(err, newBook){
if(err) {
reject(err);
}
});
});
resolve();
});
}
This function is one of a few that I am attempting to chain together, which is why I'm using promises. But I'm finding that seedBooks() resolves with anywhere between 1 and 4 of the sample books created,
function seedDB() {
removeAllBooks()
.then(function(){
return removeAllUsers();
})
.then(function(){
return seedUsers();
})
.then(function(){
return seedBooks();
})
.then(function(){
return seedBookInstances();
});
}
Am I understanding or using promises & resolve incorrectly? Any help is appreciated. Thanks!

Edit: The below explains why your code is not working and advice for, in general, how to convert non-promise code to promises. Since Mongoose produces promises, however, you should be using those instead of using new Promise. See Olegzandr's answer regarding that.
The promise is resolving immediately because you are calling resolve() immediately.
The rule of thumb when converting non-promises to promises is to promisify the smallest part of the non-promise code. In this case, it means promisifying the code to save a single item. If you do that, the collect place to call resolve() becomes clear:
function seedBook(seed) {
return new Promise(function (resolve, reject) {
Book.create(seed, function (err, newBook) {
if (err) { reject(err); } else { resolve(newBook); }
});
});
}
function seedBooks() {
return Promise.all(bookData.map(seedBook));
}
This also has the benefit of allowing you to access the returned newBooks, should you want to.

If you are using Mongoose, you can just do this:
const saveBooks = function(books) {
return books.map(function(seed) {
return Book.create(seed); // returns a promise
});
});
}
return Promise.all(saveBooks(books)).then(function(){
// all books are saved
});

You are resolving your Promise synchronously, right after you started your forEached requests, which are async. You may try the following approach instead:
function seedBooks() {
return new Promise(function(resolve,reject){
var count = 0, length = bookData.length;
bookData.forEach(function(seed){
Book.create(seed, function(err, newBook){
if(err) {
reject(err);
return;
}
if(count++ >= length ) {
resolve();
}
});
});
});
}
Here the Promise is being resolved only after all async requests are done.
Another option would be just to use Promise.all. In that approach you need to promisify all your requests in the loop, return an array of Promises and then call Promise.all(_seedBooks()).then(), where _seedBook returns an array of Promises:
function _seedBooks() {
return bookData.map(function(seed) {
return new Promise(function(resolve, reject) {
Book.create(seed, function(err, newBook) {
if(err) {
reject(err);
return;
}
resolve(newBook);
});
});
});
}
Promise.all(_seedBooks())
.then(function(result) { /* result is the array of newBook objects */ })
.catch(function(error) { /* error is the first rejected err */ })

Related

Am I chaining Promises correctly or committing a sin?

I have not worked with Javascript in a long time, so now promises are a new concept to me. I have some operations requiring more than one asynchronous call but which I want to treat as a transaction where steps do not execute if the step before failed. Currently I chain promises by nesting and I want to return a promise to the caller.
After reading the chaining section of Mozilla's Using Promises guide, I'm not sure if what I'm doing is correct or equivalent to the "callback pyramid of doom".
Is there a cleaner way to do this (besides chaining with a guard check in each then)? Am I right in my belief that in Mozilla's example it will execute each chained then even when there is an error?
myfunction(key) => {
return new Promise((outerResolve, outerReject) => {
return new Promise((resolve, reject) => {
let item = cache.get(key);
if (item) {
resolve(item);
} else {
//we didnt have the row cached, load it from store
chrome.storage.sync.get(key, function (result) {
chrome.runtime.lastError
? reject({ error: chrome.runtime.lastError.message })
: resolve(result);
});
}
}).then((resolve) => {
//Now the inner most item is resolved, we are working in the 'outer' shell
if (resolve.error) {
outerReject(resolve);
} else {
//No error, continue
new Promise((resolve, reject) => {
chrome.storage.sync.get(keyBasedOnPreviousData, function (result) {
chrome.runtime.lastError
? reject({ error: chrome.runtime.lastError.message })
: resolve(result);
});
}).then((resolve) => {
//finally return the result to the caller
if (resolve.error) {
outerReject(resolve);
} else {
outerResolve(resolve);
}
});
}
});
});
}
Subsequent then statements are not executed (until a catch) when an exception is thrown. Also, .then returns a Promise, so you don't need to create an additional, outer Promise.
Try this example:
var p = new Promise((resolve, reject) => {
console.log('first promise, resolves');
resolve();
})
.then(() => {
throw new Error('Something failed');
})
.then(() => {
console.log('then after the error');
return('result');
});
p.then(res => console.log('success: ' + res), err => console.log('error: ' + err));
You will not see "then after the error" in the console, because that happens after an exception is thrown. But if you comment the throw statement, you will get the result you expect in the Promise.
I am not sure I understand your example entirely, but I think it could be simplified like this:
myfunction(key) => {
return new Promise((resolve, reject) => {
let item = cache.get(key);
if (item) {
resolve(item);
} else {
//we didnt have the row cached, load it from store
chrome.storage.sync.get(key, function (result) {
chrome.runtime.lastError
? throw new Error(chrome.runtime.lastError.message)
: resolve(result);
});
}
}).then((previousData) => {
// keyBasedOnPreviousData is calculated based on previousData
chrome.storage.sync.get(keyBasedOnPreviousData, function (result) {
chrome.runtime.lastError
? throw new Error(chrome.runtime.lastError.message)
: return result;
});
});
}
It's a bit of a mess. This is my attempt at rewriting. A good thing to try to avoid is new Promise().
function chromeStorageGet(key) {
return new Promise( (res, rej) => {
chrome.storage.sync.get(key, result => {
if (chrome.runtime.lastError) {
rej(new Error(chrome.runtime.lastError.message))
} else {
res(result)
}
});
});
});
function myfunction(key) {
const item = cache.get(key) ? Promise.resolve(cache.get(key)) : chromeStorageGet(key);
return item.then( cacheResult => {
return chromeStorageGet(keyBasedOnPreviousData);
});
}
Why avoid new Promise()?
The reason for this is that you want to do every step with then(). If any error happened in any of the promises, every promise in the chain will fail and any subsequent then() will not get executed until there is a catch() handler.
Lots of promise based-code requires no error handlers, because promise-based functions always return promises and exceptions should flow all the back to the caller until there is something useful to be done with error handling.
Note that the exceptions to these 2 rules are in my chromeStorageGet function. A few notes here:
new Promise can be a quick and easy way to convert callback code to promise code.
It's usually a good idea to just create a little conversion layer for this callback-based code. If you need chrome.storage.sync in other places, maybe create a little utility that promisifies all its functions.
If there is only 1 'flow', you can just use a series of then() to complete the process, but sometimes you need to conditionally do other things. Just splitting up these complicated operations in a number of different functions can really help here.
But this:
const result = condition ? Promise.resolve() : Promise.reject();
Is almost always preferred to:
const result = new Promise( (res, rej) => {
if (condition) {
res();
} else {
rej();
}
}

How to manage promises in foreach when I add item in mongodb collection

I try add items in the collection of mongodb.
What is the best way to resolve this whit promises in for each?
putAlldata: function(items) {
return new Promise(function(resolve, reject) {
MongoClient.connect(config.dbDelinquency.url, function(err, db) {
if (err) {
reject(err);
} else {
resolve(db);
}
})
}).then(function(db) {
return new Promise(function(resolve, reject) {
var collection = db.collection('data');
items.forEach(function(item) {
//console.log(item);
db.collection('data').insert(item)
})
resolve(items.length);
});
});
}
use Promise.all and Array.map. I asume db.collection.insert returns a Promise. Please let me know if it doesnt.
.then(function(db) {
return Promise.all(items.map(function(item) {
return db.collection('data').insert(item)
}))
.then(() => items.length);
});
});
Rather than doing .insert for each individual item in an array of items, you can use http://mongodb.github.io/node-mongodb-native/2.2/api/Collection.html#insertMany (insertMany).
The MongoDB driver also returns promises for most methods when a callback is omitted, so you don't need to handle the promisifying yourself.
You could rewrite all your example code as:
return MongoClient.connect(url).then(db => db.collection('data').insertMany(items));
If you really wanted to iterate and do .insert individually, you could create an array of promises and use Promise.all to wait:
return Promise.all(items.map(item => db.collection('data').insert(item)));
You can do bulk insert with .insert or .insertMany.
return new Promise((resolve, reject) => {
const collection = db.collection('data');
try {
const results = db.collection.insertMany(items);
return resolve(results);
} catch (error) {
return reject(error);
}
}
As a side note.... creating the DB connection every time you want to make a call is bad practice.
You use .map in combination with Promise.all so it would be something like:
var promises = items.map(item => {
return db.collection('data').insert(item)
});
Promise.all(promises).then(...etc
In the .then of the Promise.all you can resolve your own promise.

How to use another promise in a function which returns a new promise?

I started learning Promises in JS and I am trying to replace my existing callback logic using promises. I wrote a function which returns a new promise, and also uses a promise of a database instance to retrieve the data. However, i am not sure if i am doing it right. Here is the code snippet,
usersService.js
var getUsers = function(queryObject) {
return new Promise(function(resolve, reject) {
dbConnection.find(queryObject)
.then(function(result) {
if (result.length > 0) {
resolve(result)
} else {
resolve(errorMessage.invalidUser())
}).catch(function(err) {
reject(err)
});
})
};
usersRouter.js
router.get('/users', function (req,res,next) {
var queryObject = { "userId":req.query.userId };
userService.getUsers(queryObject)
.then(function (data) { //some logic })
.catch(function (err) { //some logic });
});
Can i use resolve conditionally ?
If the answer is No, what is the right approach ?
Also, am i using the promise in a right manner, in the router?
Thanks in advance!
Since dbConnection.find returns a promise, you can return it directly and choose what will be pass when you will resolve it. No need to wrap it inside an other promise.
var getUsers = function (queryObject) {
return dbConnection.find(queryObject).then(function (result) {
if (result.length > 0) {
return result
} else {
return errorMessage.invalidUser()
}
})
};

Javascript Promise prematurely resolving

I have a function that returns a Promise, that accesses the database and pulls a few lines out, assigning them to a Javascript variable.
The issue is that my '.then' clause is being triggered even though I know the Promise hasn't resolved:
app.post("/api/hashtag", function (req, res) {
FindPopularRumours().then(function (resolveVar) {
console.log(resolveVar);
console.log();
res.send(resolveVar);
}).catch(function () {
console.log("DB Error!");
res.send("DB Error!");
});
});
And the Promise function:
function FindPopularRumours() {
return new Promise((resolve, reject) => {
var hashtags = [];
var dbPromise;
db.collection(HASHTAGS).find().forEach(function (doc) {
hashtags.push(doc.hashtag);
console.log(hashtags);
});
resolve(hashtags);
});
}
The result output is:
[ ]
['#test1']
['#test1', '#test2']
['#test1', '#test2', '#test3']
As you can see, the first line ('[ ]') should ONLY be executed AFTER the hashtags have been output. But for some reason my code seems to think the Promise has been resolved before it actually has.
EDIT1
As per Ankit's suggestion, I have amended my function to:
function FindPopularRumours() {
return new Promise((resolve, reject) => {
var hashtags = [];
db.collection(HASHTAGS).find({}, function (err, doc) {
if (!err) {
doc.forEach(function (arg) {
hashtags.push(arg.hashtag);
console.log(hashtags);
});
resolve(hashtags);
} else {
return reject(err);
}
});
});
}
This still returns the same output response as before (e.g the 'then' clause is running before the promise itself).
My POST function is still the same as before.
The db.collection.find() function is async, so you have to resolve the promise inside the callback for that, something like
function FindPopularRumours() {
return db.collection(HASHTAGS).find().toArray().then( (items) => {
return items.map( doc => doc.hashtag);
});
}
takes advantage of the Mongo toArray() method, that returns a promise directly
Please note that db.collection(HASHTAGS).find() is an asynchronous call. So, your promise is resolved before database query returns. To solve this problem, you need to re-write your database query as follows:
function FindPopularRumours() {
return new Promise((resolve, reject) => {
var hashtags = [];
var dbPromise;
db.collection(HASHTAGS).find({}, function(err, doc){
if(!err){
doc.forEach(function (arg) {
hashtags.push(arg.hashtag);
console.log(hashtags);
});
resolve(hashtags);
}else{
return reject(err);
}
});
});
}
Hope the answer helps you!

Promise pattern - using Promise.all()

I have a chain of functions for grabbing some JSON data, then inserting the data into a database. I want to wait for all of the inserts to complete, so I am trying to use Promise.all(). I know that Promise.all() needs an array (or iterable) of promises.
Here is my chain:
fetchBody().then(parseBody).then(prepareInserts).then(insertAll).then(function() {
console.log('done')
}); // Error handling and more stuff here
My code is hanging on the prepareInserts function:
// Returns an array of promises, which will be iterated over in Promise.all();
const prepareInserts = function (data) {
return new Promise(function (resolve, reject) {
const promises = data.map(function (d) {
return new Promise(function (resolve, reject) {
connection.query(queryString, [d.a, d.b, d.c, d.d], function (error) {
if (error) {
reject(error);
return;
}
resolve();
});
});
});
resolve(promises);
});
};
I think I have a fundamental misunderstanding of how I should be laying out the prepareInserts function; the queries are being executed there, which is not what I want. I want them to be inserted in the last function in the chain:
const insertAll = function (promises) {
return Promise.all(promises);
};
I think this is what you want:
const doInserts = data => {
return Promise.all(data.map(d =>
new Promise((resolve, reject) => {
connection.query(queryString, [d.a, d.b, d.c, d.d], error => {
if (error) {
reject(error);
return;
}
resolve(/* to what?? */);
});
}));
});
};
fetchBody().then(parseBody).then(doInserts).then(function() {
console.log('done')
});
You return a Promise.all() promise that resolves when all internal promises are resolved (with no value?). Each internal promise is created by mapping a data item (from data) to a promise, which is resolved or rejected depending on the query result.
If you could promisify connection.query outside of this code, it would make for a cleaner result, where you map to "promisifiedQuery" directly.

Categories

Resources