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`
});
}));
Related
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);
});
I am trying to understand how promises work by creating the Promise.all method from an exercise of this book:
https://eloquentjavascript.net/11_async.html#i_Ug+Dv9Mmsw
I tried looping through the whole array that's given as argument to the method itself, using .then for successful promises where the body of the handler pushes the result to a binding i defined previously outside of the loop, for rejected promises, i used .catch in a way that it takes the rejected value as a "reason" and rejects the main promise giving it an error
function Promise_all(promises) {
return new Promise((resolve, reject) => {
if(promises.length == 0) resolve(promises);
let fullArray = [];
for(let i=0; i<promises.length ; i++){
promises[i]
.then(x => fullArray.push(x))
.catch(reason => reject(new Error(reason)));
}
resolve(fullArray);
});
}
What i expected the function to do the following:
-Pick a promise from the "Promises" array.
-Solve the promise (if successful) by using the .then method on it with a handler function that just pushes the result to "fullArray".
-Solve the promise (if rejected) by using the .catch method on it with a handler function that simply calls the reject handler of the main promise that will be returned by "Promise_all".
-When the loop finishes, simply resolve the promise the "fullArray" of successful promises.
The code simply doesn't work the way i thought it would, using the test code of the book doesn't return the expected results:
Promise_all([]).then(array => {
console.log("This should be []:", array);
});
function soon(val) {
return new Promise(resolve => {
setTimeout(() => resolve(val), Math.random() * 500);
});
}
Promise_all([soon(1), soon(2), soon(3)]).then(array => {
console.log("This should be [1, 2, 3]:", array);
});
Promise_all([soon(1), Promise.reject("X"), soon(3)])
.then(array => {
console.log("We should not get here");
})
.catch(error => {
if (error != "X") {
console.log("Unexpected failure:", error);
}
});
The issue with the code, as #Bergi said, was that the ".then" and ".catch" callbacks are called asynchronously and as a consequence of this, "fullArray" wasn't even filled when "resolve()" was called even when it was outside of the loop. To solve this, i simply added "resolve()" inside of the promise that finishes last, to do this, i simply added a "counter" binding that has the value of the length of the "promises" array, and is reduced by 1 every time ".then" was called on a promise and when this "counter" equals 0, it calls "resolve()".
But that only solves the problem of filling the "fullArray" with the promises, not the issue of those promises being ordered correctly by the order they were called, to solve this i simply enumerated them in the array with the "i" binding of the loop, the end result was this:
function Promise_all(promises) {
return new Promise((resolve, reject) => {
if(promises.length == 0) resolve(promises);
let fullArray = [],
counter = promises.length;
for(let i=0; i< promises.length ; i++){
promises[i]
.then(x => {
fullArray[i] = x;
counter--;
if(counter == 0) resolve(fullArray)})
.catch(reason => reject(new Error(reason)));
}
});
}
Whenever you see Promise.all() being used, it is usually used with two loops, how can I make it into only one without using async and mantaining execution order?
The question is not about the order, I know promise.all preserves order, the question is about how to avoid two loops when you just need the returned value
function timeout(x){
return new Promise( resolve => {
setTimeout( () => {
return resolve(x);
},x)
})
}
const promises = [];
const results = [];
//First loop, array creation
for (i = 0; i < 20; i++) {
const promise = timeout(i*100)
promises.push(promise);
}
Promise.all(promises).then( resolvedP => {
//Second loop, async result handling
resolvedP.forEach( (timeout,i) => {
results.push({
index : i,
timeout : timeout
})
})
console.log(results);
})
//End of code
Now, this can be solved with async but in my context I can't use it, for example :
//Only one loop
for (i = 0; i < 20; i++) {
const timeoutAwait = await timeout(i*100);
results.push({
index : i,
timeout : timeoutAwait
})
}
console.log(results)
//End of code
What I have tried is the following, but the promise doesn't return the resolve value without using .then() :
for (i = 0; i < 20; i++) {
const promise = timeout(i*100)
promises.push(promise);
results.push({index : i, timeout : promise});
}
Promise.all(promises).then( resolvedP => {
resolvedP.forEach( (timeout,i) => {
results.push({
index : i,
timeout : timeout
})
})
console.log(results);
//results[0].timeout is a Promise object instead of 0
})
//End of code
So, is there any way I can make my first code sample in only one loop? Please ignore the context, is only an example.
function timeout(x) {
return new Promise(resolve => {
setTimeout(() => {
return resolve(x);
}, x);
});
}
const promises = [];
const results = [];
//First loop, array creation
for (let i = 0; i < 20; i++) {
const promise = timeout(i * 100).then(x => results.push({
index: i,
timeout: x
}));
promises.push(promise);
}
Promise.all(promises).then(() => {
console.log(results);
});
If you want to preserve execution/results order assign results using i index instead of .push
const promise = timeout(i * 100).then(x => results[i] = {
index: i,
timeout: x
});
As by the Promise.all documentation, the order will be preserved. It says about the return value:
A pending Promise in all other cases. This returned promise is then
resolved/rejected asynchronously (as soon as the stack is empty) when
all the promises in the given iterable have resolved, or if any of the
promises reject. See the example about "Asynchronicity or
synchronicity of Promise.all" below. Returned values will be in order
of the Promises passed, regardless of completion order.
(Highlighted by me.)
I find it more easy to understand if you map over an array of values instead of looping:
const timeout = x=>Promise.resolve(x);
Promise.all(
[...new Array(3).keys()]//array [0,1,2]
.map(x=>timeout(x*100))//[timeout(0*100),timeout(1*100),...
).then(
result=>result.map(//results of the timeout, map to object
(timeout,index)=>({
index,
timeout
})
)
).then(
result=>console.log('result:',result)
)
It's usually not a good idea to asynchronously mutate a shared variable (the results array). Just have the promise resolve and create the result you need in the last .then, now your promise resolves to the value you want.
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);
})
The title is confusing, sorry.
I need to look at the contents of a promise for a subsequent promise.
See my previous thread for context: How to sequentially handle asynchronous results from API?
I have working code below, and I annotated my problem:
var promises = [];
while (count) {
var promise = rp(options);
promises.push(promise);
// BEFORE NEXT PROMISE, I NEED TO GET ID OF AN ELEMENT IN THIS PROMISE'S DATA
// AND THEN CHANGE 'OPTIONS'
}
Promise.all(promises).then(values => {
for (var i = 0; i < values; i++) {
for (var j = 0; j < values[i].length; j++) {
results.push(values[i][j].text);
}
}
return res.json(results);
}, function(reason) {
trace(reason);
return res.send('Error');
});
This is a perfect example of how promises can be chained, because the result of then is a new promise.
while (count) {
var promise = rp(options);
promises.push(promise.then(function(result) {
result.options = modify(result.options); // as needed
return result.options;
});
}
In this way, each promise given to Promise.all can be preprocessed.
If one promise depends upon another (e.g. it can't be executed until the prior one has finished and provided some data), then you need to chain your promises. There is no "reaching into a promise to get some data". If you want its result, you wait for it with .then().
rp(options).then(function(data) {
// only here is the data from the first promise available
// that you can then launch the next promise operation using it
});
If you're trying to do this sequence count times (which is what your code implies), then you can create a wrapper function and call itself from the completion of each promise until the count is reached.
function run(iterations) {
var count = 0;
var options = {...}; // set up first options
var results = []; // accumulate results here
function next() {
return rp(options).then(function(data) {
++count;
if (count < iterations) {
// add anything to the results array here
// modify options here for the next run
// do next iteration
return next();
} else {
// done with iterations
// return any accumulated results here to become
// the final resolved value of the original promise
}
});
}
return next();
}
// sample usage
run(10).then(function(results) {
// process results here
}, function(err) {
// process error here
});