Promise all with inner function to be executed - javascript

I want to retrieve different HTML body at once and as soon as all results are available work with that content.
My callback solution which works looks like this (probably only relevant to read if the idea is not clear, otherwise skip ;)):
const request = require('request')
const argLength = process.argv.length
const result_array = []
let counter = 0
function executeRequest () {
for (start = 2; start <= argLength - 1; start++) {
const copy = start
function callback (error, res, body) {
const startCopy = copy
if (error) {
console.log('error')
return
}
result_array[startCopy - 2] = body.toString().length
counter++
if (counter === argLength - 2) {
console.log(result_array)
}
}
request(process.argv[start], callback)
}
}
executeRequest()
Now I try to make it running with Promises like this:
const httpRequest = require('request')
const argumentLength = process.argv.length
function fillURLArray () {
resultArray = []
for (start = 2; start < argumentLength; start++) {
resultArray[start - 2] = process.argv[start]
}
return resultArray
}
const urls = fillURLArray()
let counter = 0
function readHttp () {
const resultArray = []
Promise.all(urls.map(url => httpRequest(url, (error, res, body) => {
console.log(body.toString())
resultArray[counter++] = body.toString()
}))).then(value => {
console.log('promise counter: ' + counter++)
console.log(resultArray)
console.log('called')
})
}
readHttp()
I tried already several attempts with different promise chains but every time I get either not a result or just an empty array. So obviously the .then() function is called before the array is actually filled (at least I guess so since console.log(body.toString()) appears to print the content some time later)
Any idea how to solve this with promises?
Thank you

request is not returning promise object so have created a method that return promise object on which you do Promise.all.
function requestPromise(url){
return new Promise((resovle,reject) => {
httpRequest(url, (error, res, body) => {
if(err){
reject(err);
}
resolve(body.toString());
});
});
}
function readHttp () {
const resultArray = []
Promise.all(urls.map(url => requestPromise(url))).then(values => {
console.log("counter => ",values.length);
resultArray = resultArray.concat(values);
console.log("values=> ",values);
console.log("resultArray=> ",resultArray);
});
}

httpRequest does not return a promise so you have to make one yourself, also your resultArray is not necessary:
const makeRequest = url => new Promise((resolve, reject) => httpRequest(url, (error, res) => error ? reject(error) : resolve(res)));
Promise.all(urls.map(makeRequest))
.then(result => {
console.log(result.map(res => res.body.toString()));
console.log('called');
});

Related

How to make async/wait inside of for loop?

I'm using async await inside of for loop as below.
for (let i = 0; i < result.length; i += 1) {
try{
const client = await axios.get(
`${process.env.user}/client/${result[i].id}`
);
} catch(error){
console.log(error)
}
if (client.data.success === true) {
result[i].Name = rider.data.client.Name;
result[i].PhoneNumber = rider.data.client.Number;
}
}
But I want to make this using 'new Promise' and 'promiss.all' to make it asynclously.
But I don'k know how to make this correctly doing error handle well.
Could you recommend some advice for this? Thank you for reading it.
This can be a basic solution, i think
let playList = []
for (let i = 0; i < result.length; i += 1) {
playList.push(axios.get(
`${process.env.user}/client/${result[i].id}`
).then(res => {
if (res.data.success === true) {
result[i].Name = rider.data.client.Name;
result[i].PhoneNumber = rider.data.client.Number;
}
}).catch(ex => console.log(ex)));
}
await Promise.all(playList)
This can also be done by using a foreach loop.
The for/foreach loop can be simplified by using a map function.
Js map function is equivalent of c# select linq function.
The fat arrow in js map function is not bound to return a value unlike c# select inner function which must return a value.
await Promise.all(result.map(async r => {
let client;
try {
client = await axios.get(`${process.env.user}/client/${r.id}`);
} catch (error) {
console.log(error)
}
if (client.data.success === true) {
r.Name = rider.data.client.Name;
r.PhoneNumber = rider.data.client.Number;
}
}));
Try this
var promises = result.map(r => axios.get(`${process.env.user}/client/${r.id}`);
Promise.all(promises).then(function(values) {
console.log('All promises done');
});
The idea is that if you are awaiting something, that is promise, you can await it, or call it to get promise
Example:
function Foo()
{
return new Promise(...); // Promise of int for example
}
you can do
var p = Foo(); //you will get promise
Or
var v = await Foo(); // you will get int value when promise resolved
This is how you do it with async/await + Promise.all:
const myResult = await Promise.all(result.map(({ id }) => {
return axios.get(`${process.env.user}/client/${id}`);
}));
// deal with the result of those requests
const parsed = myResult.map(data => /* your code here */);
Here is an example using Array.map to call your function along with Promise.all. I wrapped the axios request in a function so if one of your request fails, it wont stop every other requests. If you don't mind stopping when you got an issue, look at others answers to your question.
function fakeRequest() {
return new Promise((resolve, reject) => {
setTimeout(() => {
resolve({
data: {
success: true,
client: {
Name: 'Todd',
Number: 5,
},
},
});
}, 300);
});
}
(async() => {
const result = [{}, {}, {}];
await Promise.all(result.map(async(x, xi) => {
try {
const client = await fakeRequest();
if (client.data.success === true) {
result[xi].Name = client.data.client.Name;
result[xi].PhoneNumber = client.data.client.Number;
}
} catch (err) {
console.log(err)
}
}));
console.log(result);
})();

writeFile does not wait for variable to be instantiated

I'm new to node.js and javascript in general but I am having issues understanding why the writeFile function is writing a blank file. I think the for loop should be a Promise but I am not sure how to write it.
const removeProviders = function () {
readline.question('Where is the file located? ', function(filePath) {
let providerArray = fs.readFileSync(filePath).toString().split('\r\n');
console.log(providerArray);
let importFile = '';
for (let providerId in providerArray) {
getProvider(providerArray[providerId]).then((response) => {
let providerInfo = response.data;
return providerInfo;
}).then((providerInfo) => {
let entry = createImportEntry(providerInfo);
importFile += "entry";
})
}
fs.writeFile('C:/path/to/file.txt', importFile);
You can collect all the promises from the for-loop into an Array and then pass them into Promise.all() which will resolve only after all of them resolved. If one of the promises are rejected, it will reject as well:
const promises = providerArray.map((item) => {
return getProvider(item)
.then((response) => {
let providerInfo = response.data;
return providerInfo;
})
.then((providerInfo) => {
let entry = createImportEntry(providerInfo);
importFile += "entry";
});
});
Promise.all(promises).then(() => {
fs.writeFile('C:/path/to/file.txt', importFile, err => {
if (err) {
console.error(err);
}
});
});
While doing this you could also get rid of the importFile variable and collect directly the results of your promises. Promise.all().then(results => {}) will then give you an array of all results. Thus no need for an updatable variable.
The below approach may be useful.
I don't know your getProvider return Promise. you can set promise in getProvider method
const getProviderValue = async function(providerArray) {
return new Promise((resolve, reject) {
let importFile = '';
for (let providerId in providerArray) {
await getProvider(providerArray[providerId]).then((response) => {
let providerInfo = response.data;
return providerInfo;
}).then((providerInfo) => {
let entry = createImportEntry(providerInfo);
importFile += "entry";
})
}
resolve(importFile)
})
}
const removeProviders = async function () {
readline.question('Where is the file located? ', function(filePath) {
let providerArray = fs.readFileSync(filePath).toString().split('\r\n');
console.log(providerArray);
let importFile = await getProviderValue(providerArray)
fs.writeFile('C:/path/to/file.txt', importFile);
})
}
Your for loop does not wait for the promises to resolve. A better way to approach this problem would be to use reduce.
providerArray.reduce(
(p, _, i) => {
p.then(_ => new Promise(resolve =>
getProvider(providerArray[providerId]).then((response) => {
let providerInfo = response.data;
return providerInfo;
}).then((providerInfo) => {
let entry = createImportEntry(providerInfo);
importFile += entry;
resolve();
}))
);
}
, Promise.resolve() );
You can also use Promise.all but the out data may not be in the order that you expect if you append to the importFile variable.

React Native .then seems to be running out of order

I currently have the following method in a react native class, however I think this would apply to JS in general but I might be wrong:
dbGetTemplateOptions = () => {
let dataArray = [];
let subcategories = this.state.subcategories;
subcategories.forEach(item => {
let getSubValues = new Promise(function(resolve, reject) {
resolve(item);
})
getSubValues.then((item) => this.dbGetValues(item.subCatId))
getSubValues.then((value) => console.log(2))
});
}
According to my limited js knowledge of promises, in the above I'm resolving a promise and running getSubValues.then() which would mean that each of the .then methods run AFTER the method returns.
In the above code I call the method dbGetValues(item.subCatId)
Which is this:
async dbGetValues(subCatId) {
let subValues = [];
let result = await db.transaction(tx => {
tx.executeSql(
'SELECT * FROM dr_template_relational '
+ ' INNER JOIN dr_report_categorie_values on dr_report_categorie_values.id = dr_template_relational.value_id'
+ ' WHERE dr_template_relational.subcategory_id = ' + subCatId + ' AND dr_template_relational.template_id = ' + this.state.currentTemplateId,
[],
(trans, result) => {
const sqLiteResults = result.rows._array;
sqLiteResults.forEach(el => {
subValues.push({ subCategoryId: subCatId, values: el.value_id, name: el.name, narrative: el.narrative });
})
});
},
(err) => console.error(err),
() => {
console.log(1);
return subValues;
}
);
return result;
}
Notice my console.log(2) is after the then which is calling the method.
Inside the method also notice I have console.log(1). I would expect these to run in order since I'm waiting for it to finish before the next then runs. I realize I'm incorrect because the console.log is actually.
2
2
1
1
dbGetTemplateOptions = () => {
let dataArray = [];
let subcategories = this.state.subcategories;
subcategories.forEach(item => {
let getSubValues = new Promise(function(resolve, reject) {
resolve(item);
})
getSubValues.then((item) => this.dbGetValues(item.subCatId))
getSubValues.then((value) => console.log(2))
});
}
You're resolving the promise before actually calling the asynchronous dbGetValues function. This is why the then triggers before the callbacks of dbGetValues do.
It's hard to know what changes to make without more context of what you're tying to do, but I think you might actually want something like:
dbGetTemplateOptions = () => {
let dataArray = [];
let subcategories = this.state.subcategories;
subcategories.forEach(item => {
let getSubValues = new Promise(async (resolve, reject) => {
const result = await this.dbGetValues(item.subCatId)
resolve(result);
})
getSubValues.then((value) => console.log(2))
});
}
Or to try to simplify even more:
dbGetTemplateOptions = () => {
let dataArray = [];
let subcategories = this.state.subcategories;
subcategories.forEach(async (item) => {
const result = await this.dbGetValues(item.subCatId)
// Do something with result here
console.log(2)
});
}
Obviously this is based on assumptions of what you're doing

https requests in loop, call function after last request has been made node

Assuming I am calling https a multiple times to retrieve data, and I want to call a function formatJsonToLocale at the end of the last request. Is there a way to determine when that request has ended, other than checking for the last element of the array.
let sheetsArray = []
function sheetsAsJsonById (ids) {
for (let i = 0; i < ids.length; i++) {
const queryUrl = `sheets.html`
https
.get(queryUrl, res => {
let stream = []
res
.on('data', function (data) {
stream.push(data)
})
.on('end', function () {
let data = Buffer.concat(stream)
data = JSON.parse(data)
sheetArrays.push(data['values'])
formatJsonToLocale(sheetsArray) // <----- call this one after last request
})
})
.on('error', err => {
console.error(`Error in response: ${err}`)
})
}
}
when I call formatJsonToLocale outside of the function I will have the problem that the former function might not be finished as https handles stuff asynchronously.
Any suggestions on how to handle this?
You would need to keep track of execution of async code (https.get) that is getting executed within for loop. This can be achieved using promises as below:
let sheetsArray = []
function sheetsAsJsonById (ids) {
let promises = []
for (let i = 0; i < ids.length; i++) {
const queryUrl = `sheets.html`
promises.push(makeHTTPRequest(queryUrl))
}
Promise.all(promises).then((sheetArrays) => {
formatJsonToLocale(sheetsArray)
})
}
const makeHTTPRequest = (url) => {
return new Promise((resolve, reject) => {
https
.get(url, res => {
let stream = []
res
.on('data', function (data) {
stream.push(data)
})
.on('end', function () {
let data = Buffer.concat(stream)
data = JSON.parse(data)
resolve(data)
})
.on('error', err => {
console.error(`Error in response: ${err}`)
})
})
}
If you want to stick to callbacks you could use async.each function of async node module.
Wrap https.get in a Promise, which resolves on the end event, and rejects on any error. Now you can await the promise, and call the function once the for loop is done
let sheetsArray = []
function sheetsAsJsonById(ids) {
for (let i = 0; i < ids.length; i++) {
const queryUrl = `sheets.html`
await new Promise((resolve, reject) => {
https
.get(queryUrl, res => {
let stream = []
res
.on('data', function(data) {
stream.push(data)
})
.on('end', function() {
let data = Buffer.concat(stream)
data = JSON.parse(data)
sheetsArray.push(data['values'])
resolve();
})
})
.on('error', err => {
reject(err);
})
})
}
formatJsonToLocale(sheetsArray)
}

Using Promise.all

Firstly apologies if this is a simple problem to solve but I am using node-fetch and the concept of Promises for the first time. I am calling an api endpoint which returns paginated data so i get multiple links for further data.
I am handling this by putting all the links into an array and then I want to call each endpoint in the array and just print out the data to the console for now
So my first call is
var urlArray = [];
fetch(`${BASE_URL}/leagues?api_token=${AUTH_TOKEN}`, { headers: headers })
.then(function(response){
return response.json(); // pass the data as promise to next then block
})
.then(function(json){
// Collect Paginated Results
var number_of_pages = parseInt(json.meta['pagination']['total_pages']);
for (i = 2; i < number_of_pages + 1; i++) {
urlArray.push(`${BASE_URL}?page=${i}&api_token=${AUTH_TOKEN}`)
}
})
.catch(function(error) {
console.log(error);
});
So once this done i have an array of urls i need to hit sequentially, could anyone give me some pointers please on how to then setup a promise to hit each one? or can i have something where is do it in my for loop?
Thanks
sorry if this is wrong!
At the beginning, put:
var promise_array = [];
in the for loop, replace with
for (i = 2; i < number_of_pages + 1; i++) {
var url = `${BASE_URL}?page=${i}&api_token=${AUTH_TOKEN}`;
urlArray.push(url);
promise_array.push(fetch(url));
}
At the end of the function put
Promise.all(promise_array).then(all_responses => {
// .. pluck json, then continue ...
});
You can achieve this by:
fetch(`${BASE_URL}/leagues?api_token=${AUTH_TOKEN}`, { headers: headers }).then(response => {
var json = response.json();
var numberOfPages = parseInt(json.meta['pagination']['total_pages']);
var urlArray = [];
for (i = 2; i < numberOfPages + 1; i++) {
urlArray.push(`${BASE_URL}?page=${i}&api_token=${AUTH_TOKEN}`)
}
var promises = urlArray.map(url => getSomethingByUrl(url));
return Promise.all(promises);
}).then(results => {
console.log(results);
});
You can use Array.prototype.reduce to create Promise chain so they will be executed one after another
urls.reduce((prev, item) => {
return prev.then(() => {
return someAction(item);
})
}, Promise.resolve()).then(() => {
console.log('All done');
}).catch((err) => {
console.log(err);
});
Edit:
As mentioned by Bergi, value from someAction should be returned in case it's asynchronous (return Promise). If someAction is asynchronous it must return Promise.
"use strict";
function someAction(i) {
return new Promise((resolve, reject) => {
setTimeout(() => {
console.log(i);
resolve(i);
}, i*1000);
});
}
const urls = [1,2,3,4,5];
urls.reduce((prev, item) => {
return prev.then(() => {
return someAction(item);
})
}, Promise.resolve()).then(() => {
console.log('All done');
}).catch((err) => {
console.log(err);
});

Categories