How to stop Promise.all loop when it rejects - react-native

I'm having a hard time trying to stop the loop in promise.all if one promise rejects it. Here's how I did it. Is there something wrong with this?
Promise.all(myArray.map((obj) => {
this.someFunction(obj);
}))
Here's the function I call..
someFunction(){
return new Promise(function (resolve, reject) {
....
reject()
})}

I have updated my code, it is tested and it works on my machine with the mock data I feed it with. I am not exactly sure how the rest of your code is structured but it is something likes this: Oh and you cannot break out of a map, but we will use a simple for loop because we can break out of that:
function someFunction(){
return new Promise(function (resolve, reject) {
// I will be rejeccting a boolean
// If you are resolving something, resolve it as true
reject(false)
})}
async function shouldStopLoop(){
// the boolean will come here
// if it is false, the catch block will return
// if it is true, the try block will return
let stopLoop = null;
let result = null;
try {
result = await someFunction();
return result
} catch(error) {
stopLoop = error;
return stopLoop;
}
}
function mayReturnPromiseAll() {
let myArray = ['stuf to loop over...']
let arraytoGoInPrimiseAll = [];
// Array.prototype.map cannot be stopped
// Thats why we will use a for loop and we will push the data we need
// into another array
for (var i = 0; i < myArray.length; i++) {
if (!this.someFunction(obj)) {
break;
} else {
// push things in arraytoGoInPrimiseAll
}
}
if(arraytoGoInPrimiseAll.length > 0){
return Promise.all(arraytoGoInPrimiseAll)
} else {
// do something else
}
};

Try this:
const arrayOfFunctions = myArray.map(obj => this.someFunction(obj))
Promise.all(arrayOfFunctions).then(values => {
console.log(values);
}).catch(error => {
console.log(error)
});

Related

How can I refresh datatable in Wire using refreshApex

#wire(_getContacts,{recordId:'$recordId'}) wiredContacts({error,data}){
this.dataToRefresh = data;
if (data) {
this.contacts = this.dataToRefresh.recordList;
this.ContactsRecords = this.dataToRefresh.cList;
this.contactsSize = " Case Contacts (" + this.contacts.length + ")";
}else{
//
}
};
relateContacts() {
this.showSpinner = true;
this.showtable=false;
relateContacts({contacts: this.selected, recordId: this.recordId})
.then(data => {
this.showSpinner=false;
this.showtable=true;
this.showSuccessMessage();
refreshApex(this.dataToRefresh);
//location.reload();
this.isShowModal = false;
})
.catch(error => {
console.log(error);
this.showSpinner=false;
const evt = new ShowToastEvent({
title: 'Application Error',
message: error.body.message,
variant: 'error',
mode: 'sticky'
});
this.dispatchEvent(evt);
this.showSpinner = false;
});
}
For this code, I tried refreshApex with all possible ways. but I'm not sure the miss here. I've Checked all the blogs but everywhere, the solution is mentioned.
Tried refreshApex like below :
#wire(_getContacts,{recordId:'$recordId'}) wiredContacts({data}){
this.dataToRefresh = data;
But this also does not work
Ah that is a fun one ! Your issue is using destructuring in wiredContacts as the parameter.
(The {data} or {data,error} normally works as a parameter to the function being called back, except if you have to do refresh) Try this instead.
#wire(_getContacts,{recordId:'$recordId'}) wiredContacts(value){
this.dataToRefresh = value;
const {data, error} = value;
//Rest of you code now with data and error
}
Then in your other method you can do:
method(){
refreshApex(this.dataToRefresh);
}
Salesforce does show doing this in their example code, but it’s easy to miss and experience the fun you have been having with this.
https://developer.salesforce.com/docs/component-library/documentation/en/lwc/lwc.apex_result_caching
See the last example on their page.

$nextTick running before previous line finished

I have a vue function call which is triggered when selecting a radio button but it seems that my code inside my $nextTick is running before my previous line of code is finished. I don't want to use setTimout as I don't know how fast the user connection speed is.
findOrderer() {
axios.post('/MY/ENDPOINT')
.then((response) => {
this.orderers = response.data.accounts;
console.log('FIND_ORDER', this.orderers)
...OTHER_CODE
}
rbSelected(value) {
this.findOrderer();
this.newOrderList = [];
this.$nextTick(() => {
for (var i = 0, length = this.orderers.length; i < length; i++) {
console.log('FOR')
if (value.srcElement.value === this.orderers[i].accountType) {
console.log('IF')
this.newOrderList.push(this.orderers[i]);
}
}
this.$nextTick(() => {
this.orderers = [];
this.orderers = this.newOrderList;
console.log('orderers',this.orderers)
})
})
}
Looking at the console log the 'FINE_ORDERER' console.log is inside the 'findOrderer' function call so I would have expected this to be on top or am I miss using the $nextTick
That's expected, since findOrderer() contains asynchronous code. An easy way is to simply return the promise from the method, and then await it instead of waiting for next tick:
findOrderer() {
return axios.post('/MY/ENDPOINT')
.then((response) => {
this.orderers = response.data.accounts;
console.log('FIND_ORDER', this.orderers);
});
},
rbSelected: async function(value) {
// Wait for async operation to complete first!
await this.findOrderer();
this.newOrderList = [];
for (var i = 0, length = this.orderers.length; i < length; i++) {
console.log('FOR')
if (value.srcElement.value === this.orderers[i].accountType) {
console.log('IF')
this.newOrderList.push(this.orderers[i]);
}
}
this.orderers = [];
this.orderers = this.newOrderList;
console.log('orderers',this.orderers)
}

How to Improve The axios.spread

The below code i use for doing multiple HTTP calls depending on the studentList.
It works well; however, I think the axios spread is not necessary
export default {
getFee (studentList: { studentId: string }[]) {
if (studentList.length < 1) {
Promise.resolve()
}
let promises = []
for (const student of studentList) {
if (!student.studentId) {
Promise.resolve()
}
var url = `${API_URL}/${student.studentId}`
promises.push(Axios.get(url))
}
return Axios.all(promises)
.then(Axios.spread((...args) => {
// customise the response here
return args
.map(response => response.data)
.map(data => {
// #ts-ignore
data.totalMark = data.markinPhysics + data.markinMaths + data.markinChemistry // total mark sum of marks in differnet discplines
return data
})
}))
.catch(error => {
switch (error.response.status) {
case 400:
console.log('student not found')
break
case 500:
console.log('error invoking')
break
default:
console.log('unknown error')
I have to do multiple network calls in Vue and I am using Axios.
I got it working by axios, all and axios.spread, but I think the code can be improved.
The logic is to do multiple calls for the student list and get the outputs back
Can anyone help?
Axios.all
as well as Promise.all accepts array of promises and returns a new Promise which is resolved whenever all of the given promises are resolved with an array with the result of each promise
e.g.
const promise1 = Promise.resolve('data1');
const promise2 = Promise.resolve('data2');
Promise.all([
promise1,
promise2,
]).then(results => {
// results is an array with 2 elements
console.log(results[0]); // data1
console.log(results[1]); // data2
});
you can use Axios.spread to to assign each result to a variable like this:
Promise.all([
promise1,
promise2,
]).then(Axios.spread(( result1, result2 ) => {
// args is an array with 2 elements
console.log(result1); // data1
console.log(result2); // data2
});
alternatively you can use ES6 Destructuring assignment:
Promise.all([
promise1,
promise2,
]).then(([ result1, result2 ]) => {
// args is an array with 2 elements
console.log(result1); // data1
console.log(result2); // data2
});
Unnecessary Promise.resolve()
Your Promise.resolve() function calls have no effect on the getFee method since you're not returning them
What would my implementation be
async function getFee(studentList) {
try {
const promises = studentList.reduce((acc, student) =>
student.studentId
? acc.concat(Axios.get(`${API_URL}/${student.studentId}`))
: acc
, []);
const responses = await Axios.all(promises);
return responses
.map(response => response.data)
.map(data => ({
// return new object
// with data's properties
// instead of assinging the new ones directly to the data
...data,
// total mark sum of marks in differnet discplines
totalMark: data.markinPhysics + data.markinMaths + data.markinChemistry,
}));
} catch (error) {
switch (error.response.status) {
case 400:
console.log("student not found");
break;
case 500:
console.log("error invoking");
break;
default:
console.log("unknown error");
}
}
}
export default {
getFee
}
Since you're only using args as an array, you could remove axios.spread.
axios.spread() might only be useful in older browsers now that ES2015 introduced its own spread operator. The main purpose of axios.spread() is to expand the result of axios.all() into an argument list, such that you could do:
axios.all(promiseArray).then(axios.spread(function(arg1, arg2, arg3) {
/*...*/
}))
instead of:
axios.all(promiseArray).then(function(args) {
var arg1 = args[0]
var arg2 = args[1]
var arg3 = args[2]
/*...*/
})
ES2015's rest operator does the inverse of axios.spread(), so when you combine them (as seen below), you end up with the result above, as if axios.spread() and the rest operator weren't even used:
axios.all(promiseArray).then(axios.spread(function(...args) {
var arg1 = args[0]
var arg2 = args[1]
var arg3 = args[2]
/*...*/
}))
// or newer syntax:
axios.all(promiseArray).then(axios.spread((...args) => {
const arg1 = args[0]
const arg2 = args[1]
const arg3 = args[2]
/*...*/
}))
To avoid promise chaining and improve readability, I think below can be used.
const [arg1, arg2] = await Promise.all(promises)

Is there a way to wait until a function is finished in React Native?

I'm trying to get information (true/false) from AsyncStorage in a function and create a string which is importent to fetch data in the next step. My problem is, the function is not finished until the string is required.
I tried many solutions from the internet like async function and await getItem or .done() or .then(), but none worked out for me.
//_getFetchData()
AsyncStorage.getAllKeys().then((result) => { //get all stored Keys
valuelength = result.length;
if (valuelength !== 0) {
for (let i = 0; i < valuelength; i++) {
if (result[i].includes("not") == false) { //get Keys without not
AsyncStorage.getItem(result[i]).then((resultvalue) => {
if (resultvalue === 'true') {
if (this.state.firstValue) {
this.state.channels = this.state.channels + "channel_id" + result[i];
console.log("channel: " + this.state.channels);
}
else {
this.state.channels = this.state.channels + "channel" + result[i];
}
}
});
}
return this.state.channels;
_fetchData() {
var channel = this._getFetchData();
console.log("channel required: " + channel);
}
The current behaviour is that the console displays first "channel required: " than "channel: channel_id0".
Aspects in your question are unclear:
You don't say when this.state.firstValue is set, and how that relates to what you are trying to accomplish.
You have a for-loop where you could be setting the same value multiple times.
You mutate the state rather than set it. This is not good, see this SO question for more on that.
There are somethings we can do to make your code easier to understand. Below I will show a possible refactor. Explaining what I am doing at each step. I am using async/await because it can lead to much tidier and easier to read code, rather than using promises where you can get lost in callbacks.
Get all the keys from AsyncStorage
Make sure that there is a value for all the keys.
Filter the keys so that we only include the ones that do not contain the string 'not'.
Use a Promise.all, this part is important as it basically gets all the values for each of the keys that we just found and puts them into an array called items
Each object in the items array has a key and a value property.
We then filter the items so that only the ones with a item.value === 'true' remain.
We then filter the items so that only the ones with a item.value !== 'true' remain. (this may be optional it is really dependent on what you want to do)
What do we return? You need to add that part.
Here is the refactor:
_getFetchData = async () => {
let allKeys = await AsyncStorage.getAllKeys(); // 1
if (allKeys.length) { // 2
let filteredKeys = allKeys.filter(key => !key.includes('not')); // 3
let items = await Promise.all(filteredKeys.map(async key => { // 4
let value = await AsyncStorage.getItem(key);
return { key, value }; // 5
}))
let filteredTrueItems = items.filter(item => items.value === 'true'); // 6
let filteredFalseItems = items.filter(item => items.value !== 'true'); // 7
// now you have two arrays one with the items that have the true values
// and one with the items that have the false values
// at this points you can decide what to return as it is not
// that clear from your question
// return the value that your want // 8
} else {
// return your default value if there are no keys // 8
}
}
You would call this function as follows:
_fetchData = async () => {
let channel = await this._getFetchData();
console.log("channel required: " + channel);
}
Although the above will work, it will not currently return a value as you haven't made it clear which value you wish to return. I would suggest you build upon the code that I have written here and update it so that it returns the values that you want.
Further reading
For further reading I would suggest these awesome articles by Michael Chan that discuss state
https://medium.learnreact.com/setstate-is-asynchronous-52ead919a3f0
https://medium.learnreact.com/setstate-takes-a-callback-1f71ad5d2296
https://medium.learnreact.com/setstate-takes-a-function-56eb940f84b6
I would also suggest taking some time to read up about async/await and promises
https://medium.com/#bluepnume/learn-about-promises-before-you-start-using-async-await-eb148164a9c8
And finally this article and SO question on Promise.all are quite good
https://www.taniarascia.com/promise-all-with-async-await/
Using async/await with a forEach loop
Try this instead. Async functions and Promises can be tricky to get right and can be difficult to debug but you're on the right track.
async _getFetchData() {
let channels = "";
let results = await AsyncStorage.getAllKeys();
results.forEach((result) => {
if (result.includes("not") === false) {
let item = await AsyncStorage.getItem(result);
if (item === 'true') {
console.log(`channel: ${result}`)
channels = `channel_id ${result}`;
}
}
});
return channels;
}
_fetchData() {
this._getFetchData().then((channels) => {
console.log(`channel required: ${channel}`);
});
}
what if you wrap the _getFetchData() in a Promise? This would enable you to use
var channel = this._getFetchData().then(console.log("channel required: " + channel));
Otherwise the console.log won't wait for the execution of the _getFetchData().
This is what the console.log is telling you. it just logs the string. the variable is added after the async operation is done.
UPDATE
I would try this:
//_getFetchData()
AsyncStorage.getAllKeys().then((result) => { //get all stored Keys
valuelength = result.length;
if (valuelength !== 0) {
for (let i = 0; i < valuelength; i++) {
if (result[i].includes("not") == false) { //get Keys without not
AsyncStorage.getItem(result[i]).then((resultvalue) => {
if (resultvalue === 'true') {
if (this.state.firstValue) {
this.state.channels = this.state.channels + "channel_id" + result[i];
console.log("channel: " + this.state.channels);
}
else {
this.state.channels = this.state.channels + "channel" + result[i];
}
}
});
}
return new Promise((resolve, reject) => {
this.state.channels !=== undefined ? resolve(this.state.channels) : reject(Error('error '));
}
_fetchData() {
var channel = this._getFetchData().then(console.log("channel required: " + channel));
}
maybe you must change the this.state.channels !=== undefined to an expression that's matches the default value of this.state.channels.

Pg-promise : transaction issue

I got something strange with pg-promise with a transaction with generators.
This is what I want :
Get or register a user (getOrRegisterUser)
Do batch stuff (4 inserts generator)
Finally , do the last insert (generator registerCall) with the result of getOrRegisterCurrentParkingIfProvided generator
Here is my code :
db.tx(function (t) {
return t.task.call(params, getOrRegisterUser).then(function (user) {
params.masterId = user.id; // NOTICE : MY USER ID DATABASE
return t.batch([
t.task.call(params, registerNewPhones),
t.task.call(params, registerNewPlate),
t.task.call(params, registerNewSubscriptions),
t.task.call(params, getOrRegisterCurrentParkingIfProvided)
]).then(function (result) {
params.ParkingId = (result[3] !== undefined) ? result[3].id : null;
return t.task.call(params, registerCall);
})
});
}).then(function () {
// job done
resolve();
}).catch(function (err) {
console.log(err);
reject(err);
});
I got this error message at the second generator (registerNewPhones) :
severity: 'ERREUR',
code: '23503',
detail: 'La clé (customer)=(3) n\'est pas présente dans la table « users ».',
Any way to solve this ? I tried transactions like this : https://github.com/vitaly-t/pg-promise#nested-transactions or https://github.com/vitaly-t/pg-promise#synchronous-transactions but with some unknown circumstances I still got error somewhere.
Thanks
PS: I know that the implementation of these generators aren't guilty so ...
EDIT: if you really want to see the code
let squel = require('squel');
// squel with PostgresSQL syntax
let squelPostgres = squel.useFlavour('postgres');
registerNewPhones :
// Register all new phones numbers for user
function * registerNewPhones(t) {
let params = t.ctx.context;
let findPhonesForUserQuery = squelPostgres
.select()
.from("HELPDESK.phones")
.field("number")
.where("customer = ?", params.masterId)
.toString();
let registerPhoneForUser = squelPostgres
.insert()
.into("HELPDESK.phones")
.set("customer", params.masterId);
// find the already known phone number(s) for this user
return t.any(findPhonesForUserQuery).then(function (result) {
// data
let phones = (params.hasOwnProperty("phones") ? params.phones : []);
let alreadyRegisteredPhones = result.map(function (element) {
return element.number;
});
// filter data
let phonesToRegister = phones.filter(function (aPhoneNumber) {
return alreadyRegisteredPhones.indexOf(aPhoneNumber) == -1;
});
// create queries
let queries = phonesToRegister.map(function (phone) {
return db.none(
registerPhoneForUser
.clone()
.set("number", phone)
.toString()
);
});
return t.batch(queries);
});
}
and the generator getOrRegisterUser:
function * getOrRegisterUser(t) {
let params = t.ctx.context;
// QUERIES:
let findUserQuery = squelPostgres
.select()
.from("HELPDESK.users")
.field("id")
.where("registered_id = ?", params.userId)
.toString();
let insertUserQuery = squelPostgres
.insert()
.into("HELPDESK.users")
.setFields({
name: params.userName,
registered_id: params.userId,
typeOfAccount: 'BASIC',
email: params.email
})
.returning('id')
.toString();
let user = yield t.oneOrNone(findUserQuery);
return yield user || t.one(insertUserQuery);
}
The issue is within the ES6-Generator function registerNewPhones:
return t.any(findPhonesForUserQuery)...
it doesn't yield the promise result, which is required for ES6 Generator functions.
i.e. it must be:
return yield t.any(findPhonesForUserQuery)...