How to loop request-promise API request call? - javascript

I am learning Node.JS and I am introduced to request-promise package. I use it for API call but I faced a problem where I cannot apply loop to it.
This is the example showing a simple API call:
var read_match_id = {
uri: 'https://api.steampowered.com/IDOTA2Match_570/GetMatchHistory/V001',
qs: {
match_id: "123",
key: 'XXXXXXXX'
},
json: true
};
rp(read_match_id)
.then(function (htmlString) {
// Process html...
})
.catch(function (err) {
// Crawling failed...
});
How can i have loop like this:
var match_details[];
for (i = 0; i < 5; i++) {
var read_match_details = {
uri: 'https://api.steampowered.com/IDOTA2Match_570/GetMatchDetails/V001',
qs: {
key: 'XXXXXXXXX',
match_id: match_id[i]
},
json: true // Automatically parses the JSON string in the response
};
rp(read_match_details)
.then (function(read_match){
match_details.push(read_match)//push every result to the array
}).catch(function(err) {
console.log('error');
});
}
And how can I know when all the async request are done?

request-promise uses Bluebird for Promise.
The simple solution is Promise.all(ps), where ps is array of promises.
var ps = [];
for (var i = 0; i < 5; i++) {
var read_match_details = {
uri: 'https://api.steampowered.com/IDOTA2Match_570/GetMatchDetails/V001',
qs: {
key: 'XXXXXXXXX',
match_id: match_id[i]
},
json: true // Automatically parses the JSON string in the response
};
ps.push(rp(read_match_details));
}
Promise.all(ps)
.then((results) => {
console.log(results); // Result of all resolve as an array
}).catch(err => console.log(err)); // First rejected promise
The only disadvantage of this is, this will go to catch block immediately after any of the promise is rejected. 4/5 resolved, won't matter, 1 rejected will throw it all to catch.
Alternative approach is to use Bluebird's inspection (refer this). We'll map all promises to their reflection, we can do an if/else analysis for each promise, and it will work even if any of the promise is rejected.
// After loop
ps = ps.map((promise) => promise.reflect());
Promise.all(ps)
.each(pInspection => {
if (pInspection.isFulfilled()) {
match_details.push(pInspection.value())
} else {
console.log(pInspection.reason());
}
})
.then(() => callback(match_details)); // Or however you want to proceed
Hope this will solve your problem.

Related

Unexpected promise instead of array

I'm working with node and mongo. I'm trying to run a series of parallel requests using a netlify serverless function which I'm trying to create build using mongo records. So far I have:
paralellNum = 2;
const filter = { 'Parcel': { $regex: '[0-9]' }, 'UseCode': { $exists: false } };
let records = await collection.find(filter).limit(firstNum).toArray()
console.log('number of records selected from db: ', records.length);
const fetchURL = (obj) => fetch('http://localhost:8888/.netlify/functions/meta1', {
method: 'POST',
body: JSON.stringify(obj),
headers: { 'Content-Type': 'application/json' }
});
let outputArray = [];
for (let i = 0; i < (paralellNum-1); i++) {
const record = records.pop();
const obj = {"_id":record._id,"apn":record.Parcel};
outputArray.push(fetchURL(obj));
}
console.log(outputArray);
I was expecting the output array to contain the constructed fetch requests, but instead I'm seeing:
1) [Promise]
0:
Promise {[[PromiseState]]: 'pending', [[PromiseResult]]: undefined,
Symbol(async_id_symbol): 59, Symbol(trigger_async_id_symbol): 58}
length:1
Whay am I getting a promise instead of the expected array?
You can wait for all the Promises to finish with Promise.all.
Promise.all(outputArray).then(result => {
console.log(result);
// use result here
});
fetch returns a promise for the request. If you want the response of the requests to be in your outputArray you need to change outputArray.push(fetchURL(obj)); to fetchUrl(obj).then(response => outputArray.push(response));
This waits for the fetch call to finish and then takes the response and puts it into your array.

Node.js - Promise not resolving within loop

Good day all,
I'm working on extracting some data out of PipeDrive's API using Axios for Node.js. The way that PipeDrive developed their API pagination is a bit different. Here is how their pagination indicator looks:
"additional_data": {
"pagination": {
"start": 0,
"limit": 100,
"more_items_in_collection": true,
"next_start": 100
}
}
I need to interate through all pages to extract the data, and my code has successfully done that, but I cannot get my promise to resolve for some reason.
My logic in the code is as follows:
if(more_items_in_collection){Add current page's data then re-run the same function with the next_start as a parameter}
else{Add current page's data then RESOLVE the promise to complete the function}
But this resolution never happens, even though my code works (strange).
Gurus, can you please take a look at my code and let me know why you think it won't resolve (function().then((result) => {OUTPUT}) never returns happens)?
Thanks as always!
const queryPipeDrive = (start) =>{
// Query is a full then-able Async function
return new Promise((resolve, reject) => {
// API CALL
axios({
method: 'GET',
url: pipeDriveURI,
params: {
api_token: apiKey,
start: start
}
})
// THEN DO THIS
.then((response) => {
// IF there are more items in collection on additional pages, iterate through the pages
if(response.data.additional_data.pagination.more_items_in_collection){
// Add all deals on page to the container
for (const deal of response.data.data) {
db.get('deals').push(deal) // Push deal data to the StormDB File
}
console.log(chalk.cyan(`${response.data.additional_data.pagination.next_start}`))
// Function loop created. We will loop UNTIL the 'more_items_in_collection' prop is false, then we'll resolve the promise.
queryPipeDrive(response.data.additional_data.pagination.next_start)
}else{
// Add all deals on this page to the reponse container
for (const deal of response.data.data) {
db.get('deals').push(deal)
}
db.save() // Save changes to temp DB
resolve(response.data.data) // Resolve Promise with the data from the successful call
}
})
.catch((err) => {
console.log(chalk.red(err))
reject(err)
})
})
}
Your more_items_in_collection case never resolves the promise. It just creates a new one, then does nothing with it.
Additionally, you're making your code more complicated than it needs to be by using new Promise. Axios already returns a promise, so there's no need to explicitly create a new one. Calling .then will create a new promise automatically, which resolves to whatever value you return in the callback.
const queryPipeDrive = (start) => {
// API CALL
return axios({
method: "GET",
url: pipeDriveURI,
params: {
api_token: apiKey,
start: start,
},
})
// THEN DO THIS
.then((response) => {
// IF there are more items in collection on additional pages, iterate through the pages
if (response.data.additional_data.pagination.more_items_in_collection) {
// Add all deals on page to the container
for (const deal of response.data.data) {
db.get("deals").push(deal); // Push deal data to the StormDB File
}
console.log(
chalk.cyan(`${response.data.additional_data.pagination.next_start}`)
);
// Function loop created. We will loop UNTIL the 'more_items_in_collection' prop is false, then we'll resolve the promise.
return queryPipeDrive(
response.data.additional_data.pagination.next_start
);
} else {
// Add all deals on this page to the reponse container
for (const deal of response.data.data) {
db.get("deals").push(deal);
}
db.save(); // Save changes to temp DB
return response.data.data;
}
})
.catch((err) => {
console.log(chalk.red(err));
throw err;
});
};
Besides the accepted answer.
Would you consider using this async function in await? In this way, you call the main().
const main = async start => {
const res = await queryPipeDrive(start);
if (res.isMoreItems === true) {
await main(res.nextStart);
}
};
async function queryPipeDrive(start) {
const response = await axios({
method: "GET",
url: pipeDriveURI,
params: {
api_token: apiKey,
start: start,
},
});
for (const deal of response.data.data) {
db.get("deals").push(deal);
}
if (response.data.additional_data.pagination.more_items_in_collection) {
console.log(
chalk.cyan(`${response.data.additional_data.pagination.next_start}`)
);
return {
isMoreItems: true,
nextStart: response.data.additional_data.pagination.next_start,
};
} else {
db.save(); // Save changes to temp DB
return {
isMoreItems: false,
};
}
}

what is a good way to call AJAX api multiple times in an array?

i have an array of ids, and i call api like this, i'm not sure if it's correct, what if on some id it'll fail?
const myFunc = async () => {
try {
for (const todoId of todoIds) {
await fetch(
`https://abcd123poiuy.mockapi.io/users/${
user!.id
}/todos/${todoId}`,
{
method: 'PUT',
headers: {
Accept: 'application/json',
'Content-Type': 'application/json',
},
body: JSON.stringify({complete: completeAll}),
},
);
}
setLoading(false);
} catch (clearCompletedError) {
setError(clearCompletedError);
setLoading(false);
}
}
should while loop or maybe recursion is good? pls help
I'd push all the promises in an array and do Promise.all since you want it to fail if one fails.
const promises = []
for (const todoId of todoIds) {
promises.push(fetch(`someUrlwith${todoId}`));
}
Promise.all(promises).then(() => {
// success callback
}).catch(() => {
// at least one of them failed
})
Another solution if you want to call them one at a time
function loopFetch(i) {
if (i >= todoIds.length) return; // stop when reached end
return fetch(`someUrl${todoIds[index]}`).then(() => {
return loopFetch(++i);
});
}
loopFetch(0);
Promise chaining is a concept that executes sequence of asynchronous tasks sequentially. The developer has control over the each Promise block and take action on next sequence of Promise execution.
Parallel Promise - (or) - Parallel Microtasks:
With Promise.all, Promise.race and Promise.allSettled you gain control over the settlement of all the triggered promise.
Advantage: Parallely trigger all asynchronous tasks and save time.
Remember : Promise.all will return only the first rejection despite there may be more than one rejection in your AJAX calls & Promise.race will return result of either rejected or resolved promise which ever happens sooner.
Realtime UseCase: (No chaining) using Promise.allSettled
In real world its prudent that all fetch calls will be fired at once and resolved / rejected status is know in future. In this case it makes sense to know what all fetch calls are passed or failed and take corrective actions to only that require status attention. This way save network round trips!
Example below for Promise.allSettled & Promise.all to show the difference.(Read code comments)
const dummy = [{ //Dummy data
value: 1,
delay: 100,
reject: true, //flag to explicitly reject this data
},{
value: 2,
delay: 200
},
{
value: 3,
delay: 300,
},
{
value: 4,
delay: 400,
reject:true
}];
const fakeFetch = function(req){ // Fake fetch call
return new Promise(function(resolve, reject) {
console.log("API:", req.value);
setTimeout(req.reject ? reject : resolve, req.delay, req.value);
})
}
const promiserSettled = async ()=>{
let collate = [];
for(let req of dummy){
collate.push(fakeFetch(req));
}
try{ // Promise.allSettle code block
const everthing = await Promise.allSettled(collate).then((values)=>{
throw values.filter((val) => val.status === "rejected");
})
console.log("Are we done: ", everthing);
}catch(e){
console.log("Promise.allSettled - Rejected Value:", e); // Logs all rejected data
}
try{ // Promise.all code block
const everthing = await Promise.all(collate);
console.log("Are we done: ", everthing);
}catch(e){
console.log("Promise.all - Rejected Value:", e); // Logs only one rejected data
}
};
promiserSettled(); // Execute main

Push promise functions to array. use Promise.all. Promises get resolved to early

I'm pretty new to Node.js. I have a problem with promise functions and Promise.all.
Either I misunderstood some concept or I'm doing something wrong. I've tried researching on that topic but could not find the right answer to my specific problem.
The situation is as follows.
I have a promise function to generate a pdf, that returns the path of the generated pdf.
var myNicePdfCreator={
setting1: value1,
setting2: value2,
createPdf: function(customerId){
return new Promise(function(resolve, reject){
dataBase.fetchForId(customerId).then(function(data){
somePdfLibrary.createPdf.then(function(path){
resolve(path);
},function(err){
//error creating pdf
reject(err);
});
},function(err){
//error fetching from db
reject(err);
}
})
}
}
I have a promise function that takes an email address and path to the pdf and then sends an email with the attached pdf
var myNiceMailSender={
setting1: value1,
setting2: value2,
sendMail: function(email, path){
return new Promise(function(resolve, reject){
someMailLibrary.createMail(email, title, message, attachment).then(function(status){
resolve(status);
},function(err){
reject(err);
});
});
}
}
I want to do this for every object in an array (for example get a report for every customer and then email it to them). I was trying to come up with a promise function that first creates the pdf and then sends the mail and use a forEach loop to push the promise to an array and then use Promise.all to make all the PDFs and send out the mails. but whatever I try, whenever I push a promise to an array it already gets resolved before I even use Promise.all even if I just try pushing one of both promise functions to the array.
If i do this;
var promises=[];
AllCustomers.forEach(customer){
promises.push(myNicePdfCreator.createPdf(customer.id));
});
The PDFs are directly created when I push them to the array. I don't even need to call Promise.all.
The same if I try to push the promise function to an array that sends the emails, the mails are sent instantly.
Can anyone point me in the right direction why my promises get resolved when I push them to the array?
Is there a better way to create the PDFs and then email them?
Any help appreciated thank you!
I suppose what you want is to get all the pdfs gereated before you start sending the emails. As someone already said, when you call a Promise, unless you have a .then or an await for it, its execution is not going to wait.
const promises=[];
for(const customer of AllCustomers){
promises.push(myNicePdfCreator.createPdf(customer.id));
});
Promise.all(promises).then((paths) => {
// paths is going to have an array with the paths of all the pdfs already generated
});
With this code, in Promise.all is going to wait until all pdfs are generated. So inside the then, you could send the emails.
If you want to create an array of unresolved, unprocessing promises, which create a report for each customer and then email that customer, it would look something like this:
const pfuncs = AllCustomers.map(customer => {
return async function() {
const pdfPath = await myNicePdfCreator.createPdf(customer.id);
const status = await myNiceMailSendor.sendMail(customer.email, pdfPath);
return {status, pdfPath};
}
})
This creates an array of functions -- the 'createPdf' request hasn't started to run yet, it's waiting for you to call each function in the array.
When you're ready to send the requests, you would do
const results = await Promise.all(pfuncs.map(f => f()));
And now results is an array that looks like [{status: 200, pdfPath: ''} , {status: 200, pdfPath: ''}, ...]
The promises are executed when they are declared. If you want to "lock" all the promises till all are defined you could encapsulate in a function and after declaring all, make a loop to execute it.
// Await "a" seconds to finish, reject if "a" o "b" are negative
function myPromiseFunction(a, b) {
return new Promise((res, rej) => {
setTimeout(() => {
if (a < 0 || b < 0) {
rej(0);
} else {
res(a+b);
}
}, a * 1000);
})
}
(() => {
let someData = [{a:2,b:2}, {a:10, b:4}];
// Generate promises in functions
let myPromises = someData.map((v) => {
return () => myPromiseFunction(v.a, v.b);
});
// Execute all
myPromises = myPromises.map((promise) => promise());
// ...
})();
The Promise.all function only awaits to all the promises to finish the process or any promise is rejected. For example:
All promises good:
// Await "a" seconds to finish, reject if "a" o "b" are negative
function myPromiseFunction(a, b) {
return new Promise((res, rej) => {
setTimeout(() => {
if (a < 0 || b < 0) {
rej(0);
} else {
res(a+b);
}
}, a * 1000);
})
}
(() => {
let someData = [{a:2,b:2}, {a:10, b:4}];
// Generate promises in functions
let myPromises = someData.map((v) => {
return () => myPromiseFunction(v.a, v.b);
});
// Execute all
myPromises = myPromises.map((promise) => promise());
// Await all
Promise.all(myPromises).then(res => {
console.log(res, myPromises);
}).catch(err => {
console.log(err, myPromises);
});
})();
You will print the console.log in then and will be like this:
// res: [ 4, 14 ]
// myPromises: [ Promise { 4 }, Promise { 14 } ]
But if you have a promise who fails like this:
let someData = [{a:10,b:2}, {a:4, b:-4}, {a:2, b:4}];
The second promise will be rejected by the negative value, but the first promise will not resolve (10 seconds to end) so the output of the catch will be:
// err: 0 (this is the error of the first rejected promise)
// myPromises: [
// Promise { <pending> }, // The rejected promise will not stop the pendings
// Promise { <rejected> 0 }, // The rejected promise
// Promise { 6 }, // A "resolved" or maybe "pending"
// ]

JS promise passing between functions / wait for promises

I am trying to work through JS Promises in node.js and don't get the solution for passing promises between different function.
The task
For a main logic, I need to get a json object of items from a REST API. The API handling itself is located in a api.js file.
The request to the API inthere is made through the request-promise module. I have a private makeRequest function and public helper functions, like API.getItems().
The main logic in index.js needs to wait for the API function until it can be executed.
Questions
The promise passing kind of works, but I am not sure if this is more than a coincidence. Is it correct to return a Promise which returns the responses in makeRequest?
Do I really need all the promises to make the main logic work only after waiting for the items to be setup? Is there a simpler way?
I still need to figure out, how to best handle errors from a) the makeRequest and b) the getItems functions. What's the best practice with Promises therefor? Passing Error objects?
Here is the Code that I came up with right now:
// index.js
var API = require('./lib/api');
var items;
function mainLogic() {
if (items instanceof Error) {
console.log("No items present. Stopping main logic.");
return;
}
// ... do something with items
}
API.getItems().then(function (response) {
if (response) {
console.log(response);
items = response;
mainLogic();
}
}, function (err) {
console.log(err);
});
api.js
// ./lib/api.js
var request = require('request-promise');
// constructor
var API = function () {
var api = this;
api.endpoint = "https://api.example.com/v1";
//...
};
API.prototype.getItems = function () {
var api = this;
var endpoint = '/items';
return new Promise(function (resolve, reject) {
var request = makeRequest(api, endpoint).then(function (response) {
if (200 === response.statusCode) {
resolve(response.body.items);
}
}, function (err) {
reject(false);
});
});
};
function makeRequest(api, endpoint) {
var url = api.endpoint + endpoint;
var options = {
method: 'GET',
uri: url,
body: {},
headers: {},
simple: false,
resolveWithFullResponse: true,
json: true
};
return request(options)
.then(function (response) {
console.log(response.body);
return response;
})
.catch(function (err) {
return Error(err);
});
}
module.exports = new API();
Some more background:
At first I started to make API request with the request module, that works with callbacks. Since these were called async, the items never made it to the main logic and I used to handle it with Promises.
You are missing two things here:
That you can chain promises directly and
the way promise error handling works.
You can change the return statement in makeRequest() to:
return request(options);
Since makeRequest() returns a promise, you can reuse it in getItems() and you don't have to create a new promise explicitly. The .then() function already does this for you:
return makeRequest(api, endpoint)
.then(function (response) {
if (200 === response.statusCode) {
return response.body.items;
}
else {
// throw an exception or call Promise.reject() with a proper error
}
});
If the promise returned by makeRequest() was rejected and you don't handle rejection -- like in the above code --, the promise returned by .then() will also be rejected. You can compare the behaviour to exceptions. If you don't catch one, it bubbles up the callstack.
Finally, in index.js you should use getItems() like this:
API.getItems().then(function (response) {
// Here you are sure that everything worked. No additional checks required.
// Whatever you want to do with the response, do it here.
// Don't assign response to another variable outside of this scope.
// If processing the response is complex, rather pass it to another
// function directly.
}, function (err) {
// handle the error
});
I recommend this blog post to better understand the concept of promises:
https://blog.domenic.me/youre-missing-the-point-of-promises/

Categories