How to remove the following promise catches without making the code hang? - javascript

The following code loops through some form fields. If the field is a file that has to be uploaded it runs an api.uploadPhotofunction (setting the payload once the photos has been uploaded). If the field is a normal input when the payload is set directly:
formFields.forEach(field => {
if (hasUploadFiles(field)) {
uploadPhotoPromise = new Promise((resolve, reject) => {
uploads.queued.push(file)
api.uploadPhoto(file, field).then(uploadedPhoto => {
uploads.finished.push(field)
if (uploads.queued.length === uploads.finished.length) {
payload[field.name] = uploadedPhoto
resolve()
} else {
reject()
}
}).catch(error => {
console.log('error:', error)
reject()
})
}).catch(error => {
console.log('error:', error)
})
} else {
payload[field.name] = field.value
}
})
Promise.all([uploadPhotoPromise]).then(values => {
// update action
}
The code works. However, all those catch make it look a bit messy.
I tried removed them but the code hangs if I remove any of them (the code inside Promise.all never runs). Why is this? And how to refactor this code without all those catch statements without making the it hang?
Original code (plus Bergi's suggested modification):
const buildingFormPromise = utils.mapDeep(this.buildingForm.schema, field => {
if (!field.name) return // fields not in the database
else if (utils.hasUploadFiles(field)) {
utils.eachCall(field.value, (file, index) => {
field.userId = this.user.id
this.uploads.queued.push(file)
this.$set(this.uploads.queued, index, { progress: 30 })
return api.uploadPhoto(file, field).then(uploadedPhoto => {
this.$set(this.uploads.queued, index, { progress: 100 })
return loadImage(uploadedPhoto, () => {
this.uploads.finished.push(field)
if (this.uploads.queued.length === this.uploads.finished.length) {
console.log('This runs after the code inside Promise.all')
buildingPayload[field.name] = uploadedPhoto
}
})
})
})
} else {
return Promise.resolve(buildingPayload[field.name] = field.value)
}
})
Promise.all([buildingFormPromise]).then(values => {
console.log('This runs before the files are uploaded')
})

You need to pass an array of all the promises into Promise.all, and you should avoid the Promise constructor antipattern. You can move the .catch to the very end if you don't want to handle individual upload failures.
var fieldValuePromises = formFields.map(field => {
if (hasUploadFiles(field)) {
return api.uploadPhoto(file, field).then(uploadedPhoto => {
return payload[field.name] = uploadedPhoto;
});
} else {
return Promise.resolve(payload[field.name] = field.value);
}
});
Promise.all(fieldValuePromises).then(values => {
// update action
}).catch(error => {
// at least one upload failed
console.log('error:', error)
});

Related

Can not return from a function

I have a function that looks like following
export const checkForAvailableAgent = (topicId, serviceUrl, serviceId) => {
const serviceInfo = new window.adiaLive.ServiceInfo({
topicId: topicId, // set here the topicId which you want listen for
OnError: e => {
// react to error message (optional)
console.log("error: ", e);
},
OnServiceStateChange: e => {
if (e.ConnectedAdvisers > 0) {
// there are advisers online for given topicId
console.log("studio available");
return true;
} else {
console.log("studio not available");
return false;
}
}
});
serviceInfo.connect(serviceUrl, serviceId);
};
however the return statements don't return anything when I use the function in the following manner
useEffect(() => {
const agent = checkForAvailableAgent(
`sales_${i18n.language}`,
"https://linktoserviceurl",
"serviceid"
);
// console.log("studio available is: ", agent);
}, []);
the console.log massages appear but the return statement is undefined.
any help would be appreciated.
You can not return from a callback function, as it is running asynchronously and you are not waiting for it to have a result ready.
You can however make the function itself async by returning a Promise instead of the actual result and wait until the Promise has a result ready (e.g. it is resolved):
export const checkForAvailableAgent = (topicId, serviceUrl, serviceId) => {
return new Promise((resolve, reject) => {
const serviceInfo = new window.adiaLive.ServiceInfo({
topicId: topicId, // set here the topicId which you want listen for
OnError: e => {
// react to error message (optional)
console.log("error: ", e);
reject(); // reject on failure
},
OnServiceStateChange: e => {
if (e.ConnectedAdvisers > 0) {
// there are advisers online for given topicId
console.log("studio available");
resolve(true); // resolve instead of return
} else {
console.log("studio not available");
resolve(false);
}
}
});
serviceInfo.connect(serviceUrl, serviceId);
})
};
useEffect(() => {
checkForAvailableAgent(
`sales_${i18n.language}`,
"https://linktoserviceurl",
"serviceid"
).then((agent) => { // then callback is called when the promise resolved
console.log("studio available is: ", agent);
}).catch(error => { // catch is called when promise got rejected
console.log('An error happened');
});
}, []);
The function servceInfo.OnServiceStateChange is a function into the object (seems to be an event).
I'd suggest declaring a variable on the checkForAvailableAgent like connected and change it's value when the event is called.
Then access it using checkForAvailableAgent.connected.
A version with async/await and try/catch
export const checkForAvailableAgent = (topicId, serviceUrl, serviceId) => {
return new Promise((resolve, reject) => {
const serviceInfo = new window.adiaLive.ServiceInfo({
topicId: topicId,
OnError: reject,
OnServiceStateChange: e => resolve(e.ConnectedAdvisers > 0)
});
serviceInfo.connect(serviceUrl, serviceId);
})
};
useEffect(() => {
(async () => {
try {
const isAvailable = await checkForAvailableAgent(
`sales_${i18n.language}`,
"https://linktoserviceurl",
"serviceid"
);
// console.log("Result", isAvailable)
} catch(e) {
console.error(e)
}
})()
// console.log("studio available is: ", agent);
}, []);
There are 2 possible reasons
you are not returning anything from checkForAvailableAgent.
After returning from the checkForAvailableAgent, it might be asynchronous function. You can use async & await.

Returning empty result from equalTo in Firebase Cloud Function

I need to handle the case when my database does not have any matching results for equalTo(). Currently the code below only returns if I have a matching item, in the case that there is no match it does not return at all. Please advise on how to handle this?
function getActivitySurvey(pin) {
return new Promise((resolve, reject) => {
var ref = admin.database().ref("activity-survey");
ref.orderByChild("pin").equalTo(pin.toString()).once('child_added')
.then((snapshot) => {
if (snapshot) {
resolve(snapshot);
} else {
reject(new Error('No ActivitySurvey'));
}
}).catch( () => {
reject(new Error('No snapshot'));
})
})
}
Edit:
I've since modified the code to look like the below, in the event that a pin value that does not exist in the database is specified the only line that gets output to the console is 'getActivitySurvey', the function eventually times out but never returns:
function getActivitySurvey(pin) {
return new Promise((resolve, reject) => {
console.log('getActivitySurvey')
var ref = admin.database().ref("activity-survey");
ref.orderByChild("pin").equalTo(pin.toString()).once('child_added').then((snapshot) => {
console.log('have snapshot')
if (snapshot.exists()) {
resolve(snapshot);
} else {
console.log('Rejecting ActivitySurvey')
reject('error');
}
}).catch( (err) => {
console.log('Caught error')
reject('err');
})
})
}
You cannot detect whether a child exists with just a child_added listener. You'll need to use a value listener for that.
For example:
ref.orderByChild("pin").equalTo(pin.toString())
.once('child_added')
.then((snapshot) => {
resolve(snapshot);
});
ref.orderByChild("pin").equalTo(pin.toString()).limitToFirst(1)
.once('value')
.then((snapshot) => {
if (!snapshot.exists()) {
reject(new Error('No ActivitySurvey'));
}
});
Or with only one query:
ref.orderByChild("pin").equalTo(pin.toString()).limitToFirst(1)
.once('value')
.then((snapshot) => {
if (snapshot.exists()) {
snapshot.forEach(function(child) { // will loop only once, since we use limitToFirst(1)
resolve(child);
});
}
else {
reject(new Error('No ActivitySurvey'));
}
});

How to return data along with a promise

This function handles stock account data sequentially, like a state machine, in order to place a sell order.
I need to pass the account data to each state, and I don't want to store it in global variable. How do I achieve that? Am I using promises inappropriately?
Note that each call like get_account, delete_order are async calls that returns promises. And they shouldn't pass along irrelevant data.
function sell_stocks(){
get_account_info().then(account => {
if (account.orders.length > 0){
return delete_orders(account.orders);
} else {
return "continue";
}
}).then(data => {
// Now I need variable "account"
place_sell_order(account.volume); //account undefined
}).catch(msg => {
//handle error
});
}
What about doing
return delete_orders(account.orders).then((data) => [data,account]);
And
then(([data, account]) => {...})
Or if you just want the account data
return delete_orders(account.orders).then(() => account);
And
function sell_stocks(){
get_account_info().then(account => {
if (account.orders.length > 0){
return delete_orders(account.orders).then(()=> account);
} else {
return account;
}
}).then(account => {
place_sell_order(account.volume);
}).catch(msg => {
//handle error
});
}
Or with async/await
async function sell_stocks(){
try {
const account = await get_account_info();
if (account.orders.length > 0) {
await delete_orders(account.orders)
}
return place_sell_order(account.volume);
} catch (e) {
console.log(`At sell_stocks ${e}`);
return null;
}
}
Return account from first .then() to access account at chained .then()
function sell_stocks(){
return get_account_info().then(account => {
if (account.orders.length > 0){
return account
} else {
return "continue";
}
}).then(data => {
// Now I need variable "account"
return delete_orders(data.orders)
.then(() => place_sell_order(data.volume))
}).catch(msg => {
//handle error
});
}
You can do as follows:
function sell_stocks() {
get_account_info().then((account) => {
return new Promise((resolve) => {
if (account.orders.length > 0) {
resolve(delete_orders(account.orders));
} else {
resolve('continue');
}
}).then(data => {
// Now I need variable "account"
place_sell_order(account.volume); //account undefined
}).catch(msg => {
//handle error
});
});
}
Thus keeping the account variable in the local scope.

node.js pg-promise and pagination from API

Looking at https://github.com/vitaly-t/pg-promise/wiki/Data-Imports there's a very detailed doc on how to use it for importing.
However while that works for the demoed scenario I don't know how to apply it on my case.
When I do my web call, I get the actual JSON data and a paramter in the header which gives me a value for the next page (could be a date or String or a number value).
In the example, it says:
db.tx('massive-insert', t => {
return t.sequence(index => {
return getNextData(index)
.then(data => {
if (data) {
const insert = pgp.helpers.insert(data, cs);
return t.none(insert);
}
});
});
})
.then(data => {
console.log('Total batches:', data.total, ', Duration:', data.duration);
})
.catch(error => {
console.log(error);
});
In this case, sequence(index will use index which seems to increment +1.
But in my case,
function getNextData(nextPage) {
//get the data for nextPage
.....
//get the nextPage if exists for future use
nextPage = response.next;
resolve(data);
}
My question is, how can I replace index with nextPage in this example, as each new Promise needs to use the nextPage from previous one.
LATER EDIT: And if I want to fetch info from a certain value of nextPageInfo?
For instance:
db.any('Select value from table')
.then(function(value) {
var data = value; //not working
db.tx('massive-insert', t => {
return t.sequence((index, data) => {
return getNextData(index, data)
.then(a => {
if (a) {
const insert = pgp.helpers.insert(a.data, cs);
return t.none(insert).then(() => a.nextPageInfo);
}
})
});
})
.then(data => {
// COMMIT has been executed
console.log('Total batches:', data.total, ', Duration:', data.duration);
})
.catch(error => {
// ROLLBACK has been executed
console.log(error);
})
}
Following this question, I have extended article Data Imports with the new extras section, which gives you exactly the example that you need. The example copied from the article:
function getNextData(t, index, nextPageInfo) {
// t = database transaction protocol
// NOTE: nextPageInfo = undefined when index = 0
return new Promise((resolve, reject) {
/* pull the next data, according to nextPageInfo */
/* do reject(error) on an error, to ROLLBACK changes */
if(/* there is still data left*/) {
// if whateverNextDetails = undefined, the data will be inserted,
// but the sequence will end there (as success).
resolve({data, nextPageInfo: whateverNextDetails});
} else {
resolve(null);
}
});
}
db.tx('massive-insert', t => {
return t.sequence((index, data) => {
return getNextData(t, index, data)
.then(a => {
if (a) {
const insert = pgp.helpers.insert(a.data, cs);
return t.none(insert).then(() => a.nextPageInfo);
}
})
});
})
.then(data => {
// COMMIT has been executed
console.log('Total batches:', data.total, ', Duration:', data.duration);
})
.catch(error => {
// ROLLBACK has been executed
console.log(error);
});
Please note that since we are chaining the result from getNextData to the value of nextPageInfo, then if its value is undefined, it will do the next insert, but then will end the sequence (as success).

Promise chaining with functions

I am trying to use promises for a small new project. But I have some problems with understanding how I could organize my code better, with functions. Problem is, I want my functions to handle things, and my main code to handle other things. So let's see this code:
function doSomething (isGoingToResolve = true) {
return new Promise((resolve, reject) => {
if (isGoingToResolve) {
resolve("something")
} else {
reject("something else")
}
}).then(response => {
console.log("in my function",response)
}).catch(error => {
console.log("in my function",error)
})
}
doSomething().then(response => {
console.log("in my main call", response)
})
With this code, the console will log in my function something and in my main call undefined: https://jsfiddle.net/hkacvw2g/
So I found two solutions to solve my problem, but I just don't like them:
The first one is to create a variable, use a .then() on the variable, and then return the variable (https://jsfiddle.net/hkacvw2g/1/):
function doSomething (isGoingToResolve = true) {
let promise = new Promise((resolve, reject) => {
if (isGoingToResolve) {
resolve("something")
} else {
reject("something else")
}
})
promise.then(response => {
console.log("in my function",response)
}).catch(error => {
console.log("in my function",error)
})
return promise
}
And the second solution (https://jsfiddle.net/hkacvw2g/2/):
function doSomething (isGoingToResolve = true) {
return new Promise((resolve, reject) => {
if (isGoingToResolve) {
resolve("something")
} else {
reject("something else")
}
}).then(response => {
console.log("in my function",response)
return new Promise(resolve => {
resolve(response)
})
}).catch(error => {
console.log("in my function",error)
return new Promise((resolve,reject) => {
reject(error)
})
})
}
Am I missing a better solution to solve my problem?
You can simply return the value (or re-throw the error) and it will be resolved in promise chain:
function doSomething (isGoingToResolve = true) {
return new Promise((resolve, reject) => {
if (isGoingToResolve) {
resolve("something")
} else {
reject("something else")
}
}).then(response => {
console.log("in my function",response)
return response;
}).catch(error => {
console.log("in my function",error)
throw error;
})
}
You might not want that throw error, it depends on how you want to handle your rejections. This way when you re-throw the error, you should catch it when calling the doSomething() method.
You can write a tap function, to tap into a promise chain, and do something while passing along the result, and a parallel tapCatch function, to tap into errors while rethrowing them:
const tap = fn => value => { fn(value); return value; };
const tapCatch = fn => reason => { fn(reason); throw reason; };
Now you can write your code as:
function doSomething(isGoingToResolve = true) {
(isGoingToResolve ?
Promise.resolve("something") :
Promise.reject("something else")
)
.then( tap (response => console.log("in my function", response)))
.catch(tapCatch(error => console.log("in my function", error)));
}
doSomething()
.then(response => console.log("in my main call", response));
However, actually your first solution is better. It reduces the risk of messing up the promise chain by inadvertently forgetting to, or not realizing that you have to, return the original value in then clauses, or rethrow in catch clauses which such clauses are meant only for logging purposes or other side-effects.
You could also pollute the Promise prototype with something like tap (we'll call it thenDo), making it a bit easier to use:
Promise.prototype.thenDo = function(ifFulfilled, ifRejected) {
return this.then(
value => { ifFulfilled(value); return value; },
reason => { ifRejected(reason); throw reason; });
};
Promise.prototype.catchDo = function(ifRejected) {
return this.catch(reason => { ifRejected(reason); throw reason; });
};
Now you can write
function doSomething(isGoingToResolve = true) {
(isGoingToResolve ?
Promise.resolve("something") :
Promise.reject("something else")
)
.thenDo (response => console.log("in my function", response))
.catchDo(error => console.log("in my function", error));
}

Categories