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
Related
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.
I have written a function that is being called in a loop (map) and that function is using promises. Now, I want that function to run synchronously and exit before its next instance is called.
function t1(){
let arr1 = [1,2,3,4,5];
return Promise.map(arr1, (val) =>{
const params = {
"param1" : val1
};
return t2(params);
});
}
function t2(event){
return Promise.resolve()
.then({
//do something
//code doesn't reach here in sync manner. all five instance are invoked and then code reaches here for first instance and so on
})
.then({
//promise chaining. do something more
})
}
t2 is beingcalled five times, but I want each instance to be called only after the instance before it has returned the value.
Currently its not behaving like that but invoking the function five times in parallel.
I can't use async/await due to project limitations.
The problem with your current code is that Promise.prototype.map, like forEach, does not wait for asynchronous functions called inside it to complete. (No asynchronous call will ever be waited for unless you tell the interpreter to do so explicitly with await or .then)
Have t1 await each call of t2:
async function t1(){
let arr1 = [1,2,3,4,5];
const results = [];
for (const val of arr1) {
results.push(await t2(val));
}
return results;
}
Or if you want to use reduce instead of async/await:
const delay = () => new Promise(res => setTimeout(res, 500));
function t1(){
let arr1 = [1,2,3,4,5];
return arr1.reduce((lastProm, val) => lastProm.then(
(resultArrSoFar) => t2(val)
.then(result => [...resultArrSoFar, result])
), Promise.resolve([]));
}
function t2(event){
return delay().then(() => {
console.log('iter');
return event;
});
}
t1()
.then(results => console.log('end t1', results));
Or, if you need the sequential functionality to be encapsulated in t2, then have t2 have a semi-persistent variable of the previous Promise it generated:
const delay = () => new Promise(res => setTimeout(res, 500));
const t1 = () => {
return Promise.all([1, 2, 3, 4].map(t2));
};
const t2 = (() => {
let lastProm = Promise.resolve();
return (event) => {
const nextProm = lastProm
.catch(() => null) // you may or may not want to catch here
.then(() => {
// do something with event
console.log('processing event');
return delay().then(() => event);
});
lastProm = nextProm;
return nextProm;
};
})();
t1().then(results => console.log('t1 done', results));
(function loop(index) {
const next = promiseArray[index];
if (!next) {
return;
}
next.then((response) => {
// do Something before next
loop(index + 1);
}).catch(e => {
console.error(e);
loop(index + 1);
});
})(0 /* startIndex */)
Here is what running Promises sequentially would look like when using .reduce() in combination with async/await:
async function main() {
const t2 = (v) => Promise.resolve(v*2)
const arr1 = [1,2,3,4,5];
const arr1_mapped = await arr1.reduce(async (allAsync, val) => {
const all = await allAsync
all.push(await t2(val) /* <-- your async transformation */)
return all
}, [])
console.log(arr1_mapped)
}
main()
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');
});
How do I wrap this routine inside a Promise so that I only resolve when I get all the data?
var accounts = [];
getAccounts(userId, accs => {
accs.forEach(acc => {
getAccountTx(acc.id, tx => {
accounts.push({
'id': acc.id,
'tx': tx
});
});
})
});
EDIT: Any issues if I do it like this?
function getAccountsAllAtOnce() {
var accounts = [];
var required = 0;
var done = 0;
getAccounts(userId, accs => {
required = accs.length;
accs.forEach(acc => {
getAccountTx(acc.id, tx => {
accounts.push({
'id': acc.id,
'tx': tx
});
done = done + 1;
});
})
});
while(done < required) {
// wait
}
return accounts;
}
Let's put this routine into a separate function, so it is easier to re-use it later. This function should return a promise, which will be resolved with array of accounts (also I'll modify your code as small as possible):
function getAccountsWithTx(userId) {
return new Promise((resolve, reject) => {
var accounts = [];
getAccounts(userId, accs => {
accs.forEach(acc => {
getAccountTx(acc.id, tx => {
accounts.push({
'id': acc.id,
'tx': tx
});
// resolve after we fetched all accounts
if (accs.length === accounts.length) {
resolve(accounts);
}
});
});
});
});
}
The single difference is just returning a promise and resolving after all accounts were fetched. However, callbacks tend your codebase to have this "callback hell" style, when you have a lot of nested callbacks, and it makes it hard to reason about it. You can workaround it using good discipline, but you can simplify it greatly switching to returning promises from all async functions. For example your func will look like the following:
function getAccountsWithTx(userId) {
getAccounts(userId)
.then(accs => {
const transformTx = acc => getAccountTx(acc.id)
.then(tx => ({ tx, id: acc.id }));
return Promise.all(accs.map(transformTx));
});
}
Both of them are absolutely equivalent, and there are plently of libraries to "promisify" your current callback-style functions (for example, bluebird or even native Node util.promisify). Also, with new async/await syntax it becomes even easier, because it allows to think in sync flow:
async function getAccountsWithTx(userId) {
const accs = await getUserAccounts(userId);
const transformTx = async (acc) => {
const tx = getAccountTx(acc.id);
return { tx, id: acc.id };
};
return Promise.all(accs.map(transformTx));
}
As you can see, we eliminate any nesting! It makes reasoning about code much easier, because you can read code as it will be actually executed. However, all these three options are equivalent, so it is up to you, what makes the most sense in your project and environment.
I'd split every step into its own function, and return a promise or promise array from each one. For example, getAccounts becomes:
function getAccountsAndReturnPromise(userId) {
return new Promise((resolve, reject) => {
getAccounts(userId, accounts => {
return resolve(accounts);
});
});
};
And getAccountTx resolves to an array of { id, tx } objects:
function getAccountTransactionsAndReturnPromise(accountId) {
return new Promise((resolve, reject) => {
getAccountTx(account.id, (transactions) => {
var accountWithTransactions = {
id: account.id,
transactions
};
return resolve(accountWithTransactions);
});
});
};
Then you can use Promise.all() and map() to resolve the last step to an array of values in the format you desire:
function getDataForUser(userId) {
return getAccountsAndReturnPromise(userId)
.then(accounts=>{
var accountTransactionPromises = accounts.map(account =>
getAccountTransactionsAndReturnPromise(account.id)
);
return Promise.all(accountTransactionPromises);
})
.then(allAccountsWithTransactions => {
return allAccountsWithTransactions.map(account =>{
return {
id: account.id,
tx: tx
}
});
});
}
I need to read data from API that only gives 100 results per query and a timestamp from where to get the next 100.
I've managed to do multiple requests one after another with the code below, but for some reason it never returns to the original promise. It gets stuck on the "No more orders to fetch".
app.get('/test', (req, res) => {
const getOrders = (from) => {
return request(mcfApiUrl + "changes?created_after_ts="+from+"&key="+mcfKey)
.then(xml => convert.xmlDataToJSON(xml,{explicitArray:false,mergeAttrs:true}))
.then(orders => checkForMore(orders));
}
const checkForMore = (orders) => {
return new Promise((resolve, reject) => {
if (orders['Orders']['orders'] == 100){
getOrders(orders['Orders']['time_to']);
console.log("Fetched "+ orders['Orders']['orders']+" orders");
console.log("More orders available from: "+moment(orders['Orders']['time_to']*1000).format());
}
else {
console.log("Fetched "+ orders['Orders']['orders']+" orders");
console.log("No more orders to fetch");
resolve(orders);
}
});
};
var fromdate = 1483999200;
getOrders(fromdate)
.then(output => res.send("Done")) // It never gets here
.catch(err => console.log(err));
});
What am I missing?
Your issue is that you're not resolving the checkForMore promise for all options.
const checkForMore = (orders) => {
return new Promise((resolve, reject) => {
if (orders['Orders']['orders'] == 100){
getOrders(orders['Orders']['time_to']); // <-- not resolved
}
else {
resolve(orders);
}
});
};
Just wrapping the call to getOrders with resolve will fix that.
resolve(getOrders(orders['Orders']['time_to']))
However, you don't really need to create a new promise:
const checkForMore = (orders) =>
orders['Orders']['orders'] == 100
? getOrders(orders['Orders']['time_to'])
: Promise.resolve(orders);
In fact, your entire function can be shrunk into a few lines:
const getOrders = (from) =>
request(mcfApiUrl + "changes?created_after_ts="+from+"&key="+mcfKey)
.then(xml => convert.xmlDataToJSON(xml,{explicitArray:false,mergeAttrs:true}))
.then(orders =>
orders.Orders.orders == 100
? getOrders(orders.Orders.time_to)
: Promise.resolve(orders)
);
Now, if you want to accumulate all the orders, you need to maintain some state through the recursion levels.
You can do that either with a global state or an additional parameter:
const getOrders = (from, allOrders = []) =>
// ^ accumulation container
request(mcfApiUrl + "changes?created_after_ts="+from+"&key="+mcfKey)
.then(xml => convert.xmlDataToJSON(xml,{explicitArray:false,mergeAttrs:true}))
.then(orders => {
allOrders.push(orders); // <-- accumulate
return orders.Orders.orders == 100
? getOrders(orders.Orders.time_to, allOrders) // <-- pass through recursion
: Promise.resolve(allOrders)
});