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);
});
}
Related
I'm new to javascript and I'm struggling to return a value from what seems to be a synchronous function.
Here is the calling code:
async function testPromises() {
await Excel.run(async (context) => {
const myPromise = new Promise((resolve, reject) => {
setTimeout(() => {
findLatestRoundInLMS()
.then((value) => {
console.log('Returned data = "' + value + '"');
resolve(value);
})
}, 2000);
});
});
} // end of testPromises function
Here is the function:
// Activate Sheet and find latest round
async function findLatestRoundInLMS() {
await Excel.run(async (context) => {
const sLMS = context.workbook.worksheets.getItemOrNullObject("LMS");
// Search LMS tab for latest Round
let sourceRangeLMS;
let j;
sLMS.activate();
await context.sync();
sourceRangeLMS = sLMS.getRange("A1:CC1");
sourceRangeLMS.load("values");
await context.sync();
for (j = 3; j < 53; j += 4) {
if (sourceRangeLMS.values[0][j] > 0) {
if (sourceRangeLMS.values[0][j + 4] === 0) {
console.log("LMS index for current Round, j = " + j);
break;
} // end of IF
} // end of IF
} // end of FOR
console.log("Returning current Round Index, j = " + j);
await context.sync();
return j;
});
}
I've read lots about promises and watched some good YouTube videos but still cannot get the value to return to the calling code. Any help would be much appreciated.
I've tried several versions of promises and queues using .then coding to no avail.
So basically im working on a cron job in my app that fires every 3 hours and updating users 'score' by calling the RiotApi
basically the function so far
exports.updatePlayersPoints = async () => {
console.log('STARTED UPDATING');
try {
const players = await UserLadder.findAll();
await Promise.all(
players.map(async (player) => {
const p = await RiotAccount.findOne({
where: {
userId: player.userId,
},
include: RiotRegions,
});
const beginTime = new Date(player.dataValues.createdAt);
let data;
try {
const res = await axios.get(
`https://${
p.dataValues.riot_region.dataValues.name
}.api.riotgames.com/lol/match/v4/matchlists/by-account/${
p.dataValues.accountId
}?queue=420&beginTime=${beginTime.getTime()}&api_key=${
process.env.RIOT_KEY
}`
);
data = res.data;
} catch (error) {
if (!error.response.status === 404) {
console.error(error);
}
}
if (!data) {
return;
}
let totalScore = player.dataValues.userPoints;
await Promise.all(
data.matches.map(async (match, i) => {
if (i < 15) {
const { data } = await axios.get(
`https://${p.dataValues.riot_region.dataValues.name}.api.riotgames.com/lol/match/v4/matches/${match.gameId}?api_key=${process.env.RIOT_KEY}`
);
const calculateScore = () => {
return new Promise((resolve) => {
const { stats } = _.find(
data.participants,
(o) => o.championId === match.champion
);
const killsPts = stats.kills * 2;
const deathPts = stats.deaths * -1.5;
const assistsPts = stats.assists;
const wardsPts = stats.wardsPlaced / 4;
const firstBloodPts = stats.firstBloodKill ? 3 : 0;
const firstBloodAssistPts = stats.firstBloodAssist ? 3 : 0;
const firstTowerPts = stats.firstTowerKill ? 2 : 0;
const firstTowerAssistPts = stats.firstTowerAssist ? 2 : 0;
const score =
killsPts +
deathPts +
assistsPts +
wardsPts +
firstBloodPts +
firstBloodAssistPts +
firstTowerPts +
firstTowerAssistPts;
totalScore += score;
resolve();
});
};
await calculateScore();
}
})
);
const user = await UserLadder.findOne({
where: {
userId: player.userId,
},
});
user.userPoints = parseFloat(totalScore);
user.lastGameId = data.matches[0].gameId;
await user.save();
})
);
console.log('FINISHED UPDATING');
} catch (error) {
console.error(error);
}
};
Basically it just looks up the table userladder to find the players that are signed to the ladder and for each one of these players it fires a map function that makes a request to the riotapi to get the match history of this player and then later make an inside map function to map each one of these matches.
but basically I updated it to now keep track of the game id of the last call before 3 hours so it doesn't have to make request that was already done.
user.lastGameId = data.matches[0].gameId;
but now in my second map function that maps the matches I wasn't it so that if the last game from my database matches the game id that currently being mapped I want to stop the map function and not continue this record or the ones after because it also means they all have been already counted.
but I can not seem to find a way to do it.
i tried using break; but it didn't work
any ideas?
using for loop
I tried a small test with for loop so I tried
for (let i = 0; i < 15; i++) {
await new Promise(async (resolve, reject) => {
const match = data.matches[i];
console.log(match);
resolve();
if (i === 1) {
break;
}
});
}
but I still go the same error
SyntaxError: Illegal break statement
Instead of trying to "break" a map, you should filter the matches that you want to process before you execute the map.
Something like this:
await Promise.all(
const filteredMatches = data.matches.filter(match => match.gameId > previousId);
filteredMatches.map(async (match, i) => { ...
More on filter() in javascript.
Edit: If generated id's are random and are not ordered, you can store all previous id's in a Set, and then just ask if it has been previously added
await Promise.all(
const filteredMatches = data.matches.filter(match => mySet.has(match.gameId));
filteredMatches.map(async (match, i) => { ...
More on Set in javascript.
Here's my example code:
function recursiveFetch(num) {
// EXAMPLE that recursivley fetches all todos from example API
return new Promise(resolve => {
fetch("https://jsonplaceholder.typicode.com/todos/" + num)
.then((response) => {
return response.json();
})
.then((data) => {
if (num == 0) {
console.log("Done getting TODOs");
resolve(num);
} else {
recursiveFetch(num - 1);
}
});
});
}
new Promise(resolve => {
// Just using this for this example
resolve(10);
})
.then((num) => {
// This runs fine and returns a promise which is eventually resolved
return recursiveFetch(num);
})
.then((num) => {
// This never happens?
console.log("num is now " + num);
})
I can't tell why but the second .then is never run.
If I execute this code in the Firefox console I get the output Done getting TODOs but the "num is now " log is never called?
To fix your code as it is, you need
recursiveFetch(num - 1).then(resolve)
However, there are quite a few mistakes, how about this cleaned-up one:
async function fetchAll(num) {
let data = [];
for (let i = 1; i <= num; i++) {
let t = await fetch("https://jsonplaceholder.typicode.com/todos/" + i);
data.push(await t.json())
}
return data;
}
Promise
.resolve(3)
.then(fetchAll)
.then(console.log)
You need to add a resolve inside the "else" when you call recursively the function "recursiveFecth". Since the function is returning another promise you need to resolve then, otherwise it will exit inmediately.
I've tried it and it works:
function recursiveFetch(num) {
// EXAMPLE that recursivley fetches all todos from example API
return new Promise(resolve => {
fetch("https://jsonplaceholder.typicode.com/todos/" + num)
.then((response) => {
return response.json();
})
.then((data) => {
if (num == 0) {
console.log("Done getting TODOs");
resolve(num);
} else {
resolve(recursiveFetch(num - 1));
}
});
});
}
new Promise(resolve => {
// Just using this for this example
resolve(10);
})
.then((num) => {
// This runs fine and returns a promise which is eventually resolved
return recursiveFetch(num);
})
.then((num) => {
// This never happens?
console.log("num is now " + num);
})
You need to return the recursiveFetch in the inner promise
...
.then((data) => {
if (num == 0) {
console.log("Done getting TODOs");
resolve(num);
} else {
return recursiveFetch(num - 1);
}
});
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.
New to Node.js here. I'm looking for the correct way to make N asynchronous API calls from within another function, and combining their results to use further downstream. In my case, N would be reasonably small and blocking for their execution not too bad.
In synchronous execution the implementation for combine() below should work.
If I only needed the results from one API call it would be straightforward to implement the following logic in a callback function supplied to callAPI(). Where I stumble is when I need all the results combined before before executing foo(total, [...args]).
I looked into async.whilst but wasn't able to get that to work. I'm skeptical if that actually is the correct fit to my needs. I've also looked into Promises which seems to be the correct lead but it would be nice to get reassurances before crawling into that cavernous rabbit hole. Be it that Promises is the correct way, which module is the standard to use in Node.js projects?
var http = require('http');
function callAPI(id) {
var options = {
host: 'example.com',
path: '/q/result/'.concat(id)
}
var req = http.get(options, (res) => {
var body = [];
res.on('data', (chunk) => {
body.push(chunk);
}).on('end', () => {
body = Buffer.concat(body).toString();
return body;
}).on('error', (err) => {
console.error(err);
});
});
}
function combine(inputs) {
var total = 0;
for (i=0; i < inputs.length; i++) {
total += callAPI(inputs[i]['id']);
};
console.log(total);
// call some function, foo(total, [...args])
}
Edit 1:
I attempted to follow samanime's answer below and modify the API call to return a Promise. See:
function callAPI(id) {
return Promise((resolve, reject) => {
var options = {
host: 'example.com',
path: '/q/result/'.concat(id)
}
var req = http.get(options, (res) => {
var body = [];
res.on('data', (chunk) => {
body.push(chunk);
}).on('end', () => {
body = Buffer.concat(body).toString();
resolve(body);
}).on('error', (err) => {
reject(err);
});
});
});
}
function combine(inputs) {
var combined = [];
for (i=0; i < inputs.length; i++) {
total += callAPI(inputs[i]['id']);
.then(result => {
combined.push(result);
});
};
var total = combined.reduce((a, b) => a + b, 0);
console.log(total);
// call some function, foo(total, [...args])
}
This seems to get me halfway there. If I console.log(combined) inside the then() block I can see the list building up with results from the API calls. However, I still can't access the complete combined at the "end" of the for loop. Can I attach a callback to something to run after the full list has been built? Is there a better way?
Edit 2 (My solution - per Patrick Roberts suggestion)
function callAPI(id) {
return Promise((resolve, reject) => {
var options = {
host: 'example.com',
path: '/q/result/'.concat(id)
}
var req = http.get(options, (res) => {
var body = [];
res.on('data', (chunk) => {
body.push(chunk);
}).on('end', () => {
body = parseInt(Buffer.concat(body));
resolve(body);
}).on('error', (err) => {
reject(err);
});
});
});
}
function combine(inputs) {
var combined = [];
Promise.all(inputs.map(input => callAPI(input.id)))
.then((combined) => {
var total = combined.reduce((a, b) => a + b, 0);
// foo(total, [...args])
});
};
It sounds like you can just chain together a bunch of promises, passing the data along.
Basically something like:
const combined = [];
asyncOne()
.then(result => { combined.push(result); return asyncTwo())
.then(result => { combined.push(result); return asyncThree())
// and so on
As long as each function returns a promise, you'll be all set.
If you want to run them in parallel, use Promise.all(), which will do the same thing for you:
Promise.all([asyncOne(), asyncTwo(), asyncThree() /* , etc */])
.then(combined => /* combined is an array with the results of each */)
This is by far the preferred pattern for this sort of thing.
Your edit is looking a lot better, but try this:
function callAPI(id) {
return Promise((resolve, reject) => {
var options = {
host: 'example.com',
path: '/q/result/' + id
}
http.get(options, (res) => {
var body = [];
res.on('data', (chunk) => {
body.push(chunk);
}).on('end', () => {
body = Buffer.concat(body).toString();
resolve(body);
}).on('error', reject);
});
});
}
function combine(inputs) {
Promise.all(inputs.map(input => callAPI(input.id))).then((combined) => {
// completed array of bodies
console.log(combined);
// foo(combined.length, [...args]);
}).catch((error) => {
console.log(error);
});
}
I would add a counter that keeps track of remaining API calls. Whenever an API call finishes, decrement and if its 0, you're done.
const numCalls = 10;
let remaining = numCalls;
let data = [];
function getRandomInt(min, max) {
min = Math.ceil(min);
max = Math.floor(max);
return Math.floor(Math.random() * (max - min)) + min;
}
function ajax() {
// Simulate ajax with a setTimeout for random amount of time.
setTimeout(() => {
// This is the callback when calling http.get
data.push(getRandomInt(0, 10)); // Some data from server
if (--remaining <= 0) {
// Am I the last call? Use data.
console.log(data);
console.log(data.length);
}
}, getRandomInt(1000, 3000));
}
for (let i = 0; i < numCalls; i++) {
ajax();
}