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
});
Related
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 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`
});
}));
so i have a promise that collects data from a server but only collects 50 responses at a time. i have 250 responses to collect.
i could just concate promises together like below
new Promise((resolve, reject) => {
resolve(getResults.get())
})
.then((results) => {
totalResults.concat(results)
return getResults.get()
})
.then((results) => {
totalResults.concat(results)
return getResults.get()
}).then((results) => {
totalResults.concat(results)
return getResults.get()
})
In this instance i only need 250 results so this seems a managable solution but is there a way of concating promises in a loop. so i run a loop 5 times and each time run the next promise.
Sorry i am new to promises and if this were callbacks this is what i would do.
If you want to loop and serialise the promises, not executing any other get calls once one fails, then try this loop:
async function getAllResults() { // returns a promise for 250 results
let totalResults = [];
try {
for (let i = 0; i < 5; i++) {
totalResults.push(...await getResults.get());
}
} catch(e) {};
return totalResults;
}
This uses the EcmaScript2017 async and await syntax. When not available, chain the promises with then:
function getAllResults() {
let totalResults = [];
let prom = Promise.resolve([]);
for (let i = 0; i < 5; i++) {
prom = prom.then(results => {
totalResults = totalResults.concat(results);
return getResults.get();
});
}
return prom.then(results => totalResults.concat(results));
}
Note that you should avoid the promise construction anti-pattern. It is not necessary to use new Promise here.
Also consider adding a .catch() call on the promise returned by the above function, to deal with error conditions.
Finally, be aware that concat does not modify the array you call it on. It returns the concatenated array, so you need to assign that return value. In your code you don't assign the return value, so the call has no effect.
See also JavaScript ES6 promise for loop.
Probably you just need Promise.all method.
For every request you should create a promise and put it in an array, then you wrap everything in all method and you're done.
Example (assuming that getResults.get returns a promise):
let promiseChain = [];
for(let i = 0; i <5; i++){
promiseChain.push(getResults.get());
}
Promise.all(promiseChain)
.then(callback)
You can read more about this method here:
Promise.all at MDN
EDIT
You can access data returned by the promises this way:
function callback(data){
doSomething(data[0]) //data from the first promise in the chain
...
doEventuallySomethingElse(data[4]) //data from the last promise
}
I am looping through an array, and in each loop, I add to an array of promises that is then passed into $q.all. Each chain includes a confirm() dialogue and a modal. The sequence of events for the user should be confirm() - modal - confirm() - modal. Instead, I'm getting confirm() - confirm() - modal - modal. Also, I'd like a function refreshSelection() to execute after the last modal is closed, but it currently fires as soon as the last confirm() dialogue is closed.
var promiseArr = [];
var selection = []; // [obj1, obj1, obj3...]
for (var i = 0; i < selection.length; i++) {
promiseArr.push(getData(i));
}
$q.all(promiseArr)
.then(refreshSelection);
function getData(i) {
var opts = {}; // selection[i].id is param
return $http(opts)
.then(onGetSuccess(i));
}
function onGetSuccess(i) {
return function(result) {
var shouldOpenModal = confirm(selection[i].text);
if (shouldOpenModal == true) {
openModal();
}
}
}
function openModal() {
var copyPunchesModal = $uibModal.open({
// modal template options
}
});
copyPunchesModal.result.then(otherFunc, function () {
console.log("Modal closed");
});
}
function refreshSelection() {
selection = [];
}
I have also tried the following to no avail.
//...
function getData(i) {
var opts = {}; // selection[i].id is param
return $http(opts)
.then(onGetSuccess(i))
.then(openConfirm)
.then(openModal);
}
Help is much appreciated.
Q.all doesn't specify the order in which the promises will be resolved. If you want the promises to be completed in the order of the array you need to chain the promises so that the current promise is executed inside the .then() of the previous promise.
You can do this cleanly with reduce.
I've got an answer here for ES6 promises, here it is adapted to Q:
deferred = $q.defer();
deferred.resolve();
return selection.reduce(
(accumulator, current) => accumulator.then(()=>getData(current)),
deferred.promise
);
Note that you'll also need to modify your getData function to operate on the object itself (so currentSelection.text instead of selection[i].text, for example)
You want to use a $.when function, which takes as an argument a number of promises and executes a function when they all complete. Alternatively, you can pipe them all together if you are ok with them completing in sequential order.
https://api.jquery.com/jquery.when/
I wanted to do something similar to avoid overflowing a device my program was controlling - basically I needed to wait for the async transaction to complete before starting the next one. I used promises to enforce both the order of execution, and to block until each item was complete.
// the async thing we want to do, wrapped in a promise
//
function sendMessage = (message) {
var promise = new Promise (resolve, reject) {
// send message via an async function that provides success & fail callbacks, e.g. AJAX
// replace 'asyncFunction', it's just a placeholder in this example
asyncFunction (message,
function (response) { // success callback
if (/*response is to my liking*/) {
resolve ();
} else {
reject (new Error (/*why I didn't like the response*/));
}
},
function (error) { // failure callback
reject (error);
}
};
return promise;
}
// the array we want to process in order
var myMessages = ['hello', 'world', 'how', 'are', 'you', 'today?'];
// iterator, and recursive function to loop thru array
var idx = 0;
function next () {
if (idx < myMessages.length) {
sendMessage (myMessages[idx++]).then (
next, // maps to the 'resolve' call in sendMessage
function (err) { // maps to the 'reject' calls in sendMessage
throw err; // clearly, there's an option to be smarter than this
}
)
}
}
// start the recursion
next ();
I understand using promises in simple scenarios but currently really confused on how to implement something when using a for loop and some updates to local sqlite database.
Code is as follows
surveyDataLayer.getSurveysToUpload().then(function(surveys) {
var q = $q.defer();
for (var item in surveys) {
var survey = surveys[item];
// created as a closure so i can pass in the current item due to async process
(function(survey) {
ajaxserviceAPI.postSurvey(survey).then(function(response) {
//from response update local database
surveyDataLayer.setLocalSurveyServerId(survey, response.result).then(function() {
q.resolve; // resolve promise - tried only doing this when last record also
})
});
})(survey) //pass in current survey used to pass in item into closure
}
return q.promise;
}).then(function() {
alert('Done'); // This never gets run
});
Any help or assistance would be appreciated. I'm probably struggling on how best to do async calls within loop which does another async call to update and then continue once completed.
at least promises have got me out of callback hell.
Cheers
This answer will get you laid at JS conferences (no guarantees though)
surveyDataLayer.getSurveysToUpload().then(function(surveys) {
return Promise.all(Object.keys(surveys).map(function(key) {
var survey = surveys[key];
return ajaxserviceAPI.postSurvey(survey).then(function(response){
return surveyDataLayer.setLocalSurveyServerId(survey, response.result);
});
}));
}).then(function() {
alert('Done');
});
This should work (explanations in comments):
surveyDataLayer.getSurveysToUpload().then(function(surveys) {
// array to store promises
var promises = [];
for (var item in surveys) {
var survey = surveys[item];
// created as a closure so i can pass in the current item due to async process
(function(survey) {
var promise = ajaxserviceAPI.postSurvey(survey).then(function(response){
//returning this promise (I hope it's a promise) will replace the promise created by *then*
return surveyDataLayer.setLocalSurveyServerId(survey, response.result);
});
promises.push(promise);
})(survey); //pass in current survey used to pass in item into closure
}
// wait for all promises to resolve. If one fails nothing resolves.
return $q.all(promises);
}).then(function() {
alert('Done');
});
Awesome tutorial: http://ponyfoo.com/articles/es6-promises-in-depth
You basically want to wait for all of them to finish before resolving getSurveysToUpload, yes? In that case, you can return $q.all() in your getSurveysToUpload().then()
For example (not guaranteed working code, but you should get an idea):
surveyDataLayer.getSurveysToUpload().then(function(surveys) {
var promises = [];
// This type of loop will not work in older IEs, if that's of any consideration to you
for (var item in surveys) {
var survey = surveys[item];
promises.push(ajaxserviceAPI.postSurvey(survey));
}
var allPromise = $q.all(promises)
.then(function(responses) {
// Again, we want to wait for the completion of all setLocalSurveyServerId calls
var promises = [];
for (var index = 0; index < responses.length; index++) {
var response = responses[index];
promises.push(surveyDataLayer.setLocalSurveyServerId(survey, response.result));
}
return $q.all(promises);
});
return allPromise;
}).then(function() {
alert('Done'); // This never gets run
});