Should i use Promise to solve this task? - javascript

I'm requesting items from the site and i need to check if description of the subject has word "purchsed" in it and only then save it to DB.
So, when i do something liek this:
items.forEach(function(item) {
if(!isPurchased)
saveToDb(item)
}
But it is not working (item is saved in any case), because function provided in IF statement (isPurchased) returning undefined (because of async node behavior, i think).
So, i wrote Promise notPurchased :
function notPurchased(advert) {
return new Promise(
function(resolve, reject) {
if (description.length == 0)
resolve();
return request('adverts', {'count' : 50}, function(resp) {
for (i = 0; i < resp.count; i++) {
if(resp.response.items[i].text.match('purchased') != null)
reject('This item has been purchased!');
}
resolve();
});
});
}
And then using this promise in forEach loop:
response.items.forEach(function(item) {
notPurchased(item).then(function() {
DB.storeItem(item);
});
});
Is this a good aproach? I don't have enough experience with NodeJS and it seems to me a little tricky to define a promise for simple bool function.

Well, what you can do is to loop to look for what items are not in db, and then, insert all of them with Promise.all.
Would be like this:
const itemsNotSaved = items.filter((item) => {
//Check if item is in db already.
//If it is, return false, if not, true
});
const itemsPromises = itemsNotSaved.map((item) => {
//Create a new promise for inserting item
});
//Execute every Promise
Promise.all(itemsPromises)
.then((items) => {
//No errors, items have been inserted in the database
})
.catch((error) => {
//console.log(error);
})

Related

Adapting a function that isnt chainable to return a value

I am trying to get all the pages of a pdf in one object using the pdfreader package. The function originally returns each page (as its own object) when it processes it. My goal is to write a wrapper that returns all pages as an array of page objects. Can someone explain why this didn't work?
I tried:
adding .then and a return condition - because I expected the parseFileItems method to return a value:
let pages = [];
new pdfreader.PdfReader()
.parseFileItems(pp, function(err, item) {
{
if (!item) {
return pages;
} else if (item.page) {
pages.push(lines);
rows = {};
} else if (item && item.text) {
// accumulate text items into rows object, per line
(rows[item.y] = rows[item.y] || []).push(item.text);
}
}
})
.then(() => {
console.log("done" + pages.length);
});
and got the error
TypeError: Cannot read property 'then' of undefined
The function I'm modifying (From the package documentation):
var pdfreader = require("pdfreader");
var rows = {}; // indexed by y-position
function printRows() {
Object.keys(rows) // => array of y-positions (type: float)
.sort((y1, y2) => parseFloat(y1) - parseFloat(y2)) // sort float positions
.forEach(y => console.log((rows[y] || []).join("")));
}
new pdfreader.PdfReader().parseFileItems("CV_ErhanYasar.pdf", function(
err,
item
) {
if (!item || item.page) {
// end of file, or page
printRows();
console.log("PAGE:", item.page);
rows = {}; // clear rows for next page
} else if (item.text) {
// accumulate text items into rows object, per line
(rows[item.y] = rows[item.y] || []).push(item.text);
}
});
There seem to be several issues/misconceptions at once here. Let's try to look at them once at a time.
Firstly, you seem to have thought that the outer function will return ("pass on") your callback's return value
This is not the case as you can see in the library source.
Also, it wouldn't even make sense, because the callback called once for each item. So, with 10 items, it will be invoked 10 times, and then how would parseFileItems know which of the 10 return values of your callback to pass to the outside?
It doesn't matter what you return from the callback function, as the parseFileItems function simply ignores it. Furthermore, the parseFileItems function itself doesn't return anything either. So, the result of new pdfreader.parseFileItems(...) will always evaluate to undefined (and undefined obviously has no property then).
Secondly, you seem to have thought that .then is some sort of universal chaining method for function calls.
In fact, .then is a way to chain promises, or to react on the fulfillment of a promise. In this case, there are no promises anywhere, and in particular parseFileItems doesn't returns a promise (it returns undefined as described above), so you cannot call .then on its result.
According to the docs, you are supposed to react on errors and the end of the stream yourself. So, your code would work like this:
let pages = [];
new pdfreader.PdfReader()
.parseFileItems(pp, function(err, item) {
{
if (!item) {
// ****** Here we are done! ******
console.log("done" + pages.length) // The code that was in the `then` goes here instead
} else if (item.page) {
pages.push(lines);
rows = {};
} else if (item && item.text) {
// accumulate text items into rows object, per line
(rows[item.y] = rows[item.y] || []).push(item.text);
}
}
})
However, I agree that it'd be nicer to have a promise wrapper so that you won't have to stuff all the following code inside the callback's if (!item) branch. You could achieve that like this, using new Promise:
const promisifiedParseFileItems = (pp, itemHandler) => new Promise((resolve, reject) => {
new pdfreader.PdfReader().parseFileItems(pp, (err, item) => {
if (err) {
reject(err)
} else if (!item) {
resolve()
} else {
itemHandler(item)
}
})
})
let pages = []
promisifiedParseFileItems(pp, item => {
if (item.page) {
pages.push(lines)
rows = {}
} else if (item && item.text) {
// accumulate text items into rows object, per line
(rows[item.y] = rows[item.y] || []).push(item.text)
}
}).then(() => {
console.log("done", pages.length)
}, e => {
console.error("error", e)
})
Note: You would get even nicer code with async generators but that is too much to explain here now, because the conversion from a callback to an async generator is less trivial than you may think.
If you want to chain a then, you need the callback function to return a Promise :
new pdfreader.PdfReader()
.parseFileItems(pp, function (err, item) {
return new Promise( (resolve, reject) => {
let pages = ...
// do stuff
resolve(pages);
}
})
.then( pages => {
console.log("done" + pages.length);
});

How to I make an inner promise finish looping before checking a condition?

I am still new to Promises and async coding in JavaScript. I am trying to create a function that returns a promise that iterate through an array of objects with a setTimeout. On each element, I will pass it to another function that returns a Promise. If the element doesn't satisfy a condition, I put it into another array and pass that new array into the function for a recursive call 10 more times until it satisfy the condition. Here is the code:
const promiseFunc = (item) => {
return new Promise((resolve, reject) => {
// Do something
if (some_kind_of_error) {
return reject(the_error);
} else {
return resolve({
itemName: item.name,
status: (item.isComplete === 'complete')
});
}
});
};
const func2 = (listOfItems, count) => {
return new Promise((resolve, reject) => {
if (count > 10) {
reject(new Error("Too many attempts."));
}
setTimeout(() => {
const newList = [];
listOfItems.forEach(item => {
promiseFunc(item)
.then(result => {
if(result.isCompleted !== true) {
newList.push(item);
}
});
});
if (newList.length === 0) {
return resolve(true);
} else {
console.log('Calling func2 again');
return func2(newList, count+1);
}
}, 1000);
});
};
The problem is that when I run the func2 function, I always get true even if it is suppose to recurse.
When I tried to log things out, I notice that the message Calling func2 again was not logged out in the terminal. This means that no matter what, the condition for checking newList will always be empty hence it is always resolving true and never going to the else statement.
Can someone please explain why this is the current behavior? How do I make it so that my func2 will wait for the execution of if (newList.length === 0) until my forEach loop is done?

Nested promises in NodeJS

I'm writing a service in NodeJS which retrieves a list of Items. For each one, I have to create a counter that indicates the amount of available items.
So, if the item is present in the store, the counter simply increments and resolves the promise (because I'm sure there will be at most one in stock).
Otherwise, if it is in the stock, I have to check the exact number of available pieces.
If it is not one of the two previous cases, I'll resolve the promise and I pass to the next item.
The problem is in the second case, because before resolve the main promise (of the current item) I have to wait for the call to retrieve the part counter in the warehouse is over. As it is an asynchronous code, when it currently enter in the second "else", it triggers the call for the piece counter in the stock and immediately resolves the promise, without waiting for the end of the call.
How can I solve this concatenation of promises?
This is the code:
let promises: any[] = [];
for (let i = 0, itemsLength = itemList.length; i < itemsLength; i++) {
let currentItem = itemList[i];
promises.push(
new Promise(function(resolve: Function, reject: Function) {
act({
...call the service to retrieve the item list
})
.then(function(senecaResponse: SenecaResponse < any > ) {
if (senecaResponse && senecaResponse.error) {
reject(new Error(senecaResponse.error));
}
currentItem.itemDetails = senecaResponse.response;
currentItem.counters = {
available: 0
};
if (currentItem.itemDetails && currentItem.itemDetails.length) {
for (let k = 0, detailsLength = currentItem.itemDetails.length; k < detailsLength; k++) {
let currentItemDetail = currentItem.itemDetails[k];
if (currentItemDetail.val === "IN_THE_STORE") {
currentItem.counters.available++;
resolve();
} else if (currentItemDetail.courseType === "IN_STOCK") {
act({
...call the service to retrieve the counter in stock
})
.then(function(stockResponse: SenecaResponse < any > ) {
if (stockResponse && stockResponse.error) {
reject(new Error(stockResponse.error));
} else {
currentItem.counters.available = stockResponse.response;
}
resolve();
})
.catch(function(error: Error) {
options.logger.error(error);
reject(error);
})
} else {
resolve();
}
}
} else {
resolve();
}
})
.catch(function(error: Error) {
options.logger.error(error);
reject(error);
})
})
);
}
return Promise.all(promises);
Remember that you can create chains of promises via then and catch, where each handler transforms the resolution value along the way. Since your act() apparently returns a promise, there's no need for new Promise in your code at all. Instead, just use chains.
The fact you need to do sub-queries if products exist isn't a problem; then (and catch) always return promises, so if you return a simple value from your callback, the promise they create is fulfilled with that value, but if you return a promise, the promise they create is resolved to that promise (they wait for the other promise to settle and settle the same way).
Here's a sketch of how you might do it based on the code in the question:
// Result is a promise for an array of populated items
return Promise.all(itemList.map(currentItem => {
act(/*...query using `currentItem`...*/)
.then(senecaResponse => {
if (!senecaResponse || senecaResponse.error) {
// Does this really happen? It should reject rather than fulfilling with something invalid.
throw new Error((senecaResponse && senecaResponse.error) || "Invalid response");
}
currentItem.itemDetails = senecaResponse.response;
currentItem.counters = {
available: 0
};
return Promise.all((currentItem.itemDetails || []).map(currentItemDetail => {
if (currentItemDetail.courseType === "IN_STOCK") {
return act(/*...query using `currentItemDetail`...*/).then(stockResponse => {
currentItem.counters.available = stockResponse.response;
});
}
if (currentItemDetail.val === "IN_THE_STORE") {
currentItem.counters.available++;
}
// (We don't care what value we return, so the default `undefined` is fine; we
// don't use the array from `Promise.all`
}))
.then(() => currentItem); // <== Note that this means we convert the array `Promise.all`
// fulfills with back into just the `currentItem`
});
}));

Return a promise when foreach loop ends

I'm wanting to run a forEach loop then return (resolve?) a Promise so that my next function can run. I can correctly work out when the forEach is at the end but just dont know how to resolve the Promise.
Here is my code:
addSubmissionsForms(submissionId: string, submittedFields:Array<any>): Promise<any> {
const submissionPath:firebase.database.Reference = this.rootRef.child(`submissionsByID/${submissionId}/fields`);
submittedFields.forEach(function(submittedField, i) {
let fieldId = submittedField.id;
submissionPath.child(fieldId).update(submittedField);
if(i == submittedFields.length - 1) {
console.log('finished');
}
});
}
The reason I want to return a Promise is because I want to wait until this function has run then run another function like this:
this.submissionsProvider.addSubmissionsForms(submissionKey, this.submittedFields)
.then(() => {
this.navCtrl.pop();
});
So if you want to use promises you can do the following:
As a side note you can resovle any value e.g. resolve('finished')
addSubmissionsForms(submissionId: string, submittedFields:Array<any>): Promise<any> {
return new Promise((resolve, reject) => {
const submissionPath:firebase.database.Reference = this.rootRef.child(`submissionsByID/${submissionId}/fields`);
submittedFields.forEach(function(submittedField, i) {
let fieldId = submittedField.id;
submissionPath.child(fieldId).update(submittedField);
if (i == submittedFields.length - 1) {
resolve();
}
});
reject();
});
}
If the function addSubmissionsForms does nothing async (like e.g. an ajax call or reading and writing into database) you don't really need promises because you just can execute functions one after another
addSubmissionsForms();
nextFunction();

Promises Syntax

I've looked through several promise syntax, but I'm really not understanding it (including the ones that had examples). Most of them resolved a variable, but mine is a bit more complex, and I can't seem to get it running right.
I'm using Firebase to get the current user, and want it to run the next lines AFTER I get the user.
Here's my code:
componentDidMount() {
var promise = new Promise ((resolve, reject) => {
var user = fire.auth().currentUser};
resolve(
if(user) {
console.log('Favorites: requesting favorites');
fire.database().ref('/favourites/' + user.uid).once('value').then(function (snapshot) {
var recipes_obj = snapshot.val();
let recipes = [];
for (let id in recipes_obj) {
let recipe = recipes_obj[id];
recipe.id = id;
recipes.push(recipe);
console.log("recipes: ", recipes)
}
console.log("recipes outside", recipes);
this.setState({ recipes: recipes });
}.bind(this));
} else {
console.log('Favorites: no user')
}
)
}
I've also tried it like this
componentDidMount() {
var user = new Promise ((resolve, reject) => {
resolve(fire.auth().currentUser).then(() => {
if(user) {
console.log('Favorites: requesting favorites');
fire.database().ref('/favourites/' + user.uid).once('value').then(function (snapshot) {
var recipes_obj = snapshot.val();
let recipes = [];
for (let id in recipes_obj) {
let recipe = recipes_obj[id];
recipe.id = id;
recipes.push(recipe);
console.log("recipes: ", recipes)
}
console.log("recipes outside", recipes);
this.setState({ recipes: recipes });
}.bind(this));
} else {
console.log('Favorites: no user')
}
})
})
var user = fire.auth().currentUser
This code is not asynchronous. When this line executes, you have the user immediately in var user. You don't need to use a promise to do something in response to getting the user - you already have it.
The only time you need to create a new Promise object and resolve it is when you have some work that run asynchronously and you have no other promise to indicate when that work is complete.
In your code you also don't return your new promise to the caller. This means the promise is useless. Unless you hand a promise off to another bit of code that operates in response to its resolution, it doesn't really help the situation. In other words, your promise is not doing anything here.
The only bit of code that's asynchronous in your code is the fetch from the database:
fire.database().ref('/favourites/' + user.uid).once('value')
once() returns a promise. In your code, you're calling the then() method on that promise to do more work after the result is available.
Seems like your not actually calling your promise anywhere just declaring it inside your componentDidMount? Try something like this.. BTW, never used firebase so not sure on the below api use.
componentDidMount() {
var promise = new Promise ((resolve, reject) => {
let user = ()=> fire.auth().currentUser
resolve(user)
})
promise().then((user)=>{
if(user) {
return fire.database().ref(`/favourites/${user.uid}`).once('value')
}
})
.then((snapshot)=>{
//Do other stuff with snapshot
})
}

Categories