This question already has answers here:
Wait until all promises complete even if some rejected
(20 answers)
Closed 5 years ago.
I am trying to use request-promise module to check multiple web sites. If I use Promise.all as per design, promise returns with first reject. What is the proper way to execute multiple request tasks and wait all requests to finish whether they are fulfilled or rejected? I have come up with following two functions.
CheckSitesV1 returns exception due to one rejected promise. However CheckSitesV2 waits all promises to finish whether they are they are fulfilled or rejected. I will appreciate if you can comment whether the code I write makes sense. I am using NodeJS v7.9.0
const sitesArray = ['http://www.example.com','https://doesnt-really-exist.org','http://www.httpbin.org'];
async function CheckSitesV1() {
let ps = [];
for (let i = 0; i < sitesArray.length; i++) {
let ops = {
method: 'GET',
uri:sitesArray[i],
};
const resp = await rp.get(ops);
ps.push(resp);
}
return Promise.all(ps)
}
function CheckSitesV2() {
let ps = [];
for (let i = 0; i < sitesArray.length; i++) {
let ops = {
method: 'GET',
uri:sitesArray[i],
};
ps.push(rp.get(ops));
}
return Promise.all(ps.map(p => p.catch(e => e)))
}
CheckSitesV1().then(function (result) {
console.log(result);
}).catch(function (e) {
console.log('Exception: ' + e);
});
CheckSitesV2().then(function (result) {
console.log(result);
}).catch(function (e) {
console.log('Exception: ' + e);
});
Your
function CheckSitesV2() {
let ps = [];
for (let i = 0; i < sitesArray.length; i++) {
let ops = {
method: 'GET',
uri:sitesArray[i],
};
ps.push(rp.get(ops));
}
return Promise.all(ps.map(p => p.catch(e => e)))
}
is totally fine. I can only suggest refactor a bit for readability:
function CheckSitesV2() {
let ps = sitesArray
.map(site => rp.get({method: 'GET', uri: site}).catch(e => e));
return Promise.all(ps);
}
Regarding async try this
async function CheckSitesV1() {
let results = [];
for (let i = 0; i < sitesArray.length; i++) {
let opts = {
method: "GET",
uri: sitesArray[i]
};
const resp = await rp.get(opts).catch(e => e);
results.push(resp);
}
return results;
}
CheckSitesV1().then(console.log)
Related
I am ordering some items with their priorities. I used a loop for that. However, I get some weird outputs like [1,1,2,2,3] instead of [1,2,3,4,5](these are priorities btw). The loop is below.
const switchPriority = async function(catId, srcI, destI){
let indexofCat;
try {
for (let i = 0; i < data[0].length; i++) {
const element = data[0][i];
if(element.id === catId){
indexofCat = i;
}
}
let Item = Parse.Object.extend('Item')
let itemQuery = new Parse.Query(Item)
for (let i = (srcI>destI?destI:srcI); i < (srcI>destI?(srcI+1):(destI+1)); i++) {
let id = data[1][indexofCat][i].id;
let item = await itemQuery.get(id);
item.set("priority",i+1);
await item.save();
}
} catch (error) {
alert(error)
}
}
Why there is such a problem? When I add alert to the loop, with some delay it gives proper outputs. How can I solve this ? I am appreciate for your help.
I don't know if you forgot, but it's necessary use the word async like this await works. You can insert your code into a function:
async function parse(){
for (let i = (srcI); i < (destI); i++) {
// alert("index in loop "+ i);
let id = data[1][indexofCat][i].id;
let item = await itemQuery.get(id);
// alert(item.get("name"));
item.set("priority",i+1);
await item.save();
}
}
Look at for more details: https://developer.mozilla.org/pt-BR/docs/Web/JavaScript/Reference/Statements/async_function
You can also promisify a function that you needs to guarantee that it be executed before the next instruction. How?
function createPromise (parameter) {
return new Promise((resolve, reject) => {
resolve(console.log(parameter))
})
}
async function test(){
console.log('First')
await createPromise('Promise')
console.log('Second')
}
test()
Look at: https://developer.mozilla.org/pt-BR/docs/Web/JavaScript/Reference/Global_Objects/Promise
This question already has answers here:
How do I return the response from an asynchronous call?
(41 answers)
Closed 1 year ago.
I'm trying to get the value returned from an API query, but in all the ways I've done it, it either returns Undefined or [object Promise]. I've tried several solutions and I still haven't got something that works. Below is the last code I made to see if it worked but again without success.
function generateLink(link) {
const url = 'https://api.rebrandly.com/v1/links';
const options = {
method: 'POST',
uri: url,
headers: {'Content-Type': 'application/json', apikey: 'xxxxxxxxxxxxxxxxxxxxx'},
body: JSON.stringify({destination: link})
};
return requestPromise(options).then(response => {
if ( response.statusCode === 200 ) {
return response.body
}
return Promise.reject(response.statusCode)
})
}
...
bot.onText(/\/offers (.+)/, function onEchoText(msg, match) {
console.log(match[1]);
if (isNaN(match[1])) {
bot.sendMessage(msg.from.id, 'Enter a valid number! \nExample: /offers 5');
} else {
client.execute('aliexpress.affiliate.product.query', {
'app_signature': 'defualt',
'category_ids': '701,702,200001081,200001388,200001385,200386159,100000616,100001205,5090301',
'target_currency': 'USD',
'target_language': 'EN',
'tracking_id': 'defualt',
'ship_to_country': 'US',
}, function (error, response) {
var code = response.resp_result.resp_code;
var mesage = response.resp_result.resp_msg;
if (code === 200) {
var i;
var temCupom = [];
var link = [];
var itemList = response.resp_result.result.products.product;
for (i = 0; i < match[1]; i++) {
temCupom[i] = itemList[i].promo_code_info ? "🏷 <b>There's a coupon!</b>: " + itemList[i].promo_code_info.promo_code : "";
(async () => {
link[i] = generateLink(itemList[i].promotion_link).then(body => { return body.shortUrl })
})();
bot.sendPhoto(msg.chat.id, itemList[i].product_main_image_url, {
caption: "❤ <b>Promotion</b> ❤\n" +
"💵 <b>Price</b>: " + Number(itemList[i].target_sale_price).toLocaleString('en-us', { style: 'currency', currency: 'USD' }) + "\n" +
"🛒 <b>Link</b>: " + link[i] + "\n" +
temCupom[i],
});
}
}
else {
bot.sendMessage(msg.from.id, 'Deu errado! ' + mesage);
}
})
}
});
link[i] need to return the links generated with the API
Option 1 (with async/await):
if (code === 200) {
var i;
var link = [];
var itemList = response.resp_result.result.products.product;
for (i = 0; i < match[1]; i++) {
link[i] = await generateLink(itemList[i].promotion_link).then(body => { return JSON.parse(body).shortUrl })
}
}
Option 2 (with Promise):
if (code === 200) {
var link = [];
var itemList = response.resp_result.result.products.product;
for (let i = 0; i < match[1]; i++) {
generateLink(itemList[i].promotion_link).then(body => { link[i] = JSON.parse(body).shortUrl })
}
}
Option 3 (Promise.all takes all URLs in parallel mode):
if (code === 200) {
const itemList = response.resp_result.result.products.product;
const requests = itemList.slice(0, match[1])
.map(item => generateLink(item.promotion_link).then(body => JSON.parse(body).shortUrl));
const links = await Promise.all(requests);
}
===============================
Updated:
After all, we realized that the body was a string and it was necessary to do JSON.parse
You need to assign the result in the then block.
You can make some simple changes, like putting link[i] = result into the then block, but there are problems like the fact that value of i will be the same. Also, if you want the values populated after the for loop, you'll notice that they have not been filled. You need to wait till they are all resolved (faking with timeout below).
function generateLink(link) {
return new Promise((resolve, reject) => {
setTimeout(() => {
resolve(Math.random())
}, 1)
})
}
if (200 === 200) {
var i;
var link = [];
var MAX = 10;
for (i = 0; i < MAX; i++) {
generateLink('🤷♂️').then(result => {
link[i] = result; // no bueno - since i will be 10 when this is resolved
})
}
console.log(link) // still not resolved
setTimeout(() => {
console.log(link) // 🤦♂️ [undefined * 9, value]
}, 10)
}
// async version (note use of `async`)
(async function(){
var MAX = 10;
var link = []
for (i = 0; i < MAX; i++) {
link[i] = await generateLink('🤷♂️')
}
console.log(link)// 👍
})()
So you can add more complexity to make it work, but async/await is likely the way to go if you can support it.
for (i = 0; i < match[1]; i++) {
link[i] = await generateLink(itemList[i].promotion_link).then((body) => {
return body.shortUrl;
});
}
Also if you use await the promises will execute in series, which you might not want. Resolving in parallel is faster, but you could also run into issue if you're going to have too many network requests they'll need to be throttled, so async await may work better)
The return Promise.all([photoArray]) returns an empty array, seemingly not waiting for the callFB to return its promise that then pushes into the array.
I am not sure what I am doing wrong but am relatively new to Promises with for loops and Ifs.
I am not sure exactly if I am using the correct number of Promises but I seem to not be able to get the 3rd tier Promise.all to wait for the for loop to actually finish (in this scenario, the for loop has to look through many item so this is causing an issue where it is not triggering callFeedback for all the items it should before context.done() gets called.
I have tried using Q.all also for the Promise.all([photoArray]) but have been unable to get that working.
module.exports = function (context, myBlob) {
var res = myBlob
var promiseResolved = checkPhoto(res,context);
var promiseResolved2 = checkVideo(res,context);
Promise.all([promiseResolved, promiseResolved2]).then(function(results){
context.log(results[0], results[1]);
// context.done();
});
});
};
};
function checkPhoto(res, context){
return new Promise((resolve, reject) => {
if (res.photos.length > 0) {
var photoArray = [];
for (var j = 0; j < res.photos.length; j++) {
if (res.photos[j].feedbackId !== null){
var feedbackId = res.photos[j].feedbackId;
var callFB = callFeedback(context, feedbackId);
Promise.all([callFB]).then(function(results){
photoArray.push(results[0]);
});
} else {
photoArray.push("Photo " + j + " has no feedback");
}
}
return Promise.all([photoArray]).then(function(results){
context.log("end results: " + results);
resolve(photoArray);
});
} else {
resolve('No photos');
}
})
}
function checkVideo(res, context){
return new Promise((resolve, reject) => {
same as checkPhoto
})
}
function callFeedback(context, feedbackId) {
return new Promise((resolve, reject) => {
var requestUrl = url.parse( URL );
var requestBody = {
"id": feedbackId
};
// send message to httptrigger to message bot
var body = JSON.stringify( requestBody );
const requestOptions = {
standard
};
var request = https.request(requestOptions, function(res) {
var data ="";
res.on('data', function (chunk) {
data += chunk
// context.log('Data: ' + data)
});
res.on('end', function () {
resolve("callFeedback: " + true);
})
}).on('error', function(error) {
});
request.write(body);
request.end();
})
}
The code suffers from promise construction antipattern. If there's already a promise (Promise.all(...)), there is never a need to create a new one.
Wrong behaviour is caused by that Promise.all(...).then(...) promise isn't chained. Errors aren't handled and photoArray.push(results[0]) causes race conditions because it is evaluated later than Promise.all([photoArray])....
In case things should be processed in parallel:
function checkPhoto(res, context){
if (res.photos.length > 0) {
var photoArray = [];
for (var j = 0; j < res.photos.length; j++) {
if (res.photos[j].feedbackId !== null){
var feedbackId = res.photos[j].feedbackId;
var callFB = callFeedback(context, feedbackId);
// likely no need to wait for callFB result
// and no need for Promise.all
photoArray.push(callFB);
} else {
photoArray.push("Photo " + j + " has no feedback");
}
}
return Promise.all(photoArray); // not [photoArray]
} else {
return 'No photos';
};
}
callFB promises don't depend on each other and thus can safely be resolved concurrently. This allows to process requests faster.
Promise.all serves a good purpose only if it's used to resolve promises in parallel, while the original code tried to resolve the results (results[0]).
In case things should be processed in series the function benefits from async..await:
async function checkPhoto(res, context){
if (res.photos.length > 0) {
var photoArray = [];
for (var j = 0; j < res.photos.length; j++) {
if (res.photos[j].feedbackId !== null){
var feedbackId = res.photos[j].feedbackId;
const callFBResult = await callFeedback(context, feedbackId);
// no need for Promise.all
photoArray.push(callFBResult);
} else {
photoArray.push("Photo " + j + " has no feedback");
}
}
return photoArray; // no need for Promise.all, the array contains results
} else {
return 'No photos';
};
}
Add try..catch to taste.
I was practicing Express 4.x and noticed the following:
app.get('/fake', function(req, res) {
var obj = [];
for (let i = 0; i < 3; i++) {
jsf.resolve(fakeSchema).then(function(iter) {
obj.push(iter);
});
}
res.send(obj);
});
So, going to that route, I get "[ ]", while I was expecting to receive an array of 3 (fake) documents.
FYI, when logging each loop, I can clearly see the documents generated, even inside the array.
Any explanation?
Your jsf.resolve functiion is async so you can use async/await for this to perform task in sync manner.
https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Statements/async_function
app.get('/fake', async function(req, res) {
var obj = [];
for (let i = 0; i < 3; i++) {
try {
var iter = await jsf.resolve(fakeSchema);
obj.push(iter);
} catch (e) {}
}
res.send(obj);
});
Although #Nishant's provided answer works, I suggest using this approach.
let jsf = {};
// faking your jsf.resolve method
jsf.resolve = (param) => {
return new Promise((resolve) => {
setTimeout(() => {
resolve(Math.random());
}, 1000);
})
};
let fakeSchema = {};
let obj = [];
let promises = [];
for (let i = 0; i !== 3; i++) {
promises.push(jsf.resolve(fakeSchema).then(function (iter) {
obj.push(iter);
}));
}
Promise.all(promises).then(() => {
console.log(obj);
});
This allows all the promises to run concurrently, imagine your jsx.resolve takes a long time to complete, using await would freeze your entire appp.
As opposed to this. Note the runtime.
(async () => {
let jsf = {};
// faking your jsf.resolve method
jsf.resolve = (param) => {
return new Promise((resolve) => {
setTimeout(() => {
resolve(Math.random());
}, 1000);
})
};
let fakeSchema = {};
let obj = [];
for (let i = 0; i !== 3; i++) {
obj.push(await jsf.resolve(fakeSchema));
}
console.log(obj);
})();
#Nishant Dixit's answer also correct!
You can try this simple solution also, if you like :
app.get('/fake', function(req, res) {
var obj = [];
for (let i = 0; i < 3; i++) {
try {
jsf.resolve(fakeSchema).then(function(iter) {
obj.push(iter);
res.send(obj);
} catch (e) {
res.send(e);
}
});
};
});
For the following function, I have to add a timeout after every GET request in array ajaxUrls. All the XHR GET request are in array ajaxUrls.
function getAllSearchResultProfiles(searchAjaxUrl) {
var ajaxUrls = [];
for (var i = 0; i < numResults; i += resultsPerPage) {
ajaxUrls.push(searchAjaxUrl + "&start=" + i);
}
return Promise.all(ajaxUrls.map(getSearchResultsForOnePage))
.then(function(responses) {
return responses.map(function(response) {
if (response.meta.total === 0) {
return [];
}
return response.result.searchResults.map(function(searchResult) {
return (searchResult);
});
});
})
.then(function(searchProfiles) {
return [].concat.apply([], searchProfiles);
})
.catch(function(responses) {
console.error('error ', responses);
});
}
function getSearchResultsForOnePage(url) {
return fetch(url, {
credentials: 'include'
})
.then(function(response) {
return response.json();
});
}
I want a certain timeout or delay after every GET request. I am facing difficulty in where exactly to add the timeout.
If you want to make requests in serial, you shouldn't use Promise.all, which initializes everything in parallel - better to use a reduce that awaits the previous iteration's resolution and awaits a promise-timeout. For example:
async function getAllSearchResultProfiles(searchAjaxUrl) {
const ajaxUrls = [];
for (let i = 0; i < numResults; i += resultsPerPage) {
ajaxUrls.push(searchAjaxUrl + "&start=" + i);
}
const responses = await ajaxUrls.reduce(async (lastPromise, url) => {
const accum = await lastPromise;
await new Promise(resolve => setTimeout(resolve, 1000));
const response = await getSearchResultsForOnePage(url);
return [...accum, response];
}, Promise.resolve([]));
// do stuff with responses
const searchProfiles = responses.map(response => (
response.meta.total === 0
? []
: response.result.searchResults
));
return [].concat(...searchProfiles);
}
Note that only asynchronous operations should be passed from one .then to another; synchronous code should not be chained with .then, just use variables and write the code out as normal.
I find a simple for loop in an async function to be the most readable, even if not necessarily the most succinct for things like this. As long as the function is an async function you can also create a nice pause() function that makes the code very easy to understand when you come back later.
I've simplified a bit, but this should give you a good idea:
function pause(time) {
// handy pause function to await
return new Promise(resolve => setTimeout(resolve, time))
}
async function getAllSearchResultProfiles(searchAjaxUrl) {
var ajaxUrls = [];
for (var i = 0; i < 5; i++) {
ajaxUrls.push(searchAjaxUrl + "&start=" + i);
}
let responses = []
for (url of ajaxUrls) {
// just loop though and await
console.log("sending request")
let response = await getSearchResultsForOnePage(url)
console.log("recieved: ", response)
responses.push(response)
await pause(1000) // wait one second
}
//responses.map() and other manilpulations etc...
return responses
}
function getSearchResultsForOnePage(url) {
//fake fetch
return Promise.resolve(url)
}
getAllSearchResultProfiles("Test")
.then(console.log)
If you want to add a delay in every request then add a setTimout() in your function which fetches data from api
function getSearchResultsForOnePage(url) {
return new Promise((resolve, reject) => {
fetch(url, {
credentials: 'include'
})
.then(response => reresponse.json())
.then(data => {
let timeout = 1000;
setTimeout(() => resolve(data), timeout);
});
}