How to avoid nesting promises when handling errors from multiple APIs - javascript

I’m working on an application where I need to make requests to two apis. I’m using cognito to handle authentication, and then a lambda that communicates with a database. But, I don’t think that my problem is specific to either of those implementations. It could arise with any two apis.
I’m trying to write the process of signing up a new user. I need to create a new user in cognito so that the new user is able to login, and then I need to create a corresponding user in the database that will store the non-authentication related data for the user. If one of the api requests encounters an error, then I need to delete the item that I created in the other api.
My current implementation is basically this:
const signUpNewUser = (userInfo) => {
API.post("user", "/user", userInfo)
.then((response) => {
return COGNITO.post("user", "/user", response.newUserID);
})
.then((res) => {
//BOTH REQUESTS OCCURED WITH NO ERRORS
})
.catch((error) => {
if (error.origin === "COGNITO_ERROR") {
//IF DB CHANGES CONFIRMED BUT COGNITO FAILED, DELETE CREATED GUEST IN DB
return API.delete("guest", "/guest", userInfo);
} else if (error.origin === "DATABASE_ERROR") {
//IF DB CHANGES FAILED THEN COGNITO HAS NOT RUN YET, SO DON'T NEED TO DELETE IN THIS CASE
}
});
};
This follows the pattern I see demonstrated on the internet. However, I’m having trouble distinguishing cognito errors from database errors. In the above code I sort them by error.origin but they don’t actually have a property that reliably indicates their origin. This problem must be common when working with multiple api’s that you don’t control, but I’m having trouble finding a good solution.
It feels I need to nest promises in this situation. I could nest a catch after API.Post and COGNITO.post, and use that catch to throw a new error that has an origin property. Then that would bubble up and get caught by the final catch that handles all errors. Like this:
const signUpNewUser2 = (userInfo) => {
API.post("user", "/user", userInfo)
.catch((err) => {
let parsedError = err;
parsedError.origin = "DATABASE_ERROR";
throw parsedError;
})
.then((response) => {
let newGuestID = response.id;
return COGNITO.post("user", "/user", newGuestID)
.then((res) => {
return res;
})
.catch((err) => {
let parsedError = err;
parsedError.origin = "COGNITO_ERROR";
throw parsedError;
});
})
.then((res) => {
//BOTH REQUESTS OCCURED WITH NO ERRORS
})
.catch((error) => {
if (error.origin === "COGNITO_ERROR") {
//IF DB CHANGES CONFIRMED BUT COGNITO FAILED, DELETE CREATED GUEST IN DB
return API.delete("guest", "/guest", guestInfo);
} else if (error.origin === "DATABASE_ERROR") {
//IF DB CHANGES FAILED THEN COGNITO HAS NOT RUN YET, SO DON'T NEED TO DELETE IN THIS CASE
}
});
};
But everything I've read says you should avoid nesting promises.
Alternatively, I could put API.post and COGNITO.post in separate functions with internal .then .catch statements, and then have those functions return a promise or throw an error with an added property to indicate origin. But I've seen people say that just hides the problem and makes code harder to follow.
The standard patter I see is that you have one catch, towards the end of a .then chain, that knows how to handle multiple kinds of errors. But if you don’t control the APIs you are working with, how can you confidently sort those errors? Is there something basic about the nature of errors in js that I'm missing?

Because you want to make the API calls in serial, this should be pretty easy to manage. All you need to do is do COGNITO.post in a .then after the first API call - no need to insert another .catch in between.
const signUpNewUser2 = (userInfo) => {
API.post("user", "/user", userInfo)
.then((response) => {
let newGuestID = response.id;
return COGNITO.post("user", "/user", newGuestID)
.then(handleBothSuccess)
.catch((err) => {
// COGNITO failed
return API.delete("guest", "/guest", guestInfo);
});
})
.then((res) => {
//BOTH REQUESTS OCCURED WITH NO ERRORS
})
.catch((error) => {
// Some error other than COGNITO failing occurred
});
};
There's nothing wrong with nesting Promises when the control flow you need to implement requires it - or with declaring the .then or .catch functions in separate standalone variables first, which avoids visual nesting.
Alternatively, consider async/await, which may be clearer to follow.
const signUpNewUser2 = async (userInfo) => {
let newGuestId;
try {
newGuestId = await API.post("user", "/user", userInfo);
} catch (e) {
// API failed, do something here if you want...
return;
}
let cognitoResponse;
try {
cognitoResponse = await COGNITO.post("user", "/user", newGuestID);
} catch (e) {
// COGNITO failed
// If deleting throws, the error will percolate to the caller
return API.delete("guest", "/guest", guestInfo);
}
//BOTH REQUESTS OCCURED WITH NO ERRORS
};

Related

Handling errors in javascript/react when calling backend APIs

I am building a react-native app, and I am starting to implement a more robust and sophisticated error-handling system, specifically for handling server errors when making http requests. Here is a basic example of how I am currently making http requests in my app.
I have a 'client.js' file which is essentially just a wrapper around axios. I have a 'get' method that looks like this:
const get = async (endpoint, config = {}) => {
try {
const result = await axios.get(domain + endpoint, config);
return result;
} catch (error) {
throw new Error(error.message);
}
};
Then, I have a file for each api endpoint that I need to access. For example, I have a 'posts.js' file, and in that file I have a 'getPosts' method:
const getPosts = async (userID, page, pageSize) => {
try {
const response = await client.get(
`${endpoint}?userID=${userID}&page=${page}&pageSize=${pageSize}`
);
return response.data;
} catch (error) {
throw new Error(error.message);
}
};
And then finally, in the component that is calling getPosts, I have a function that looks something like this:
const loadPosts = async () => {
try {
const response = await getPosts();
// do something with the response from the server
} catch (error) {
// display an error message to the client
}
}
Obviously this is a very simple example of what a request might look like, but this is the basic structure that I use throughout my app. The problem I am having is that it seems very repetitive and messy to have to wrap almost all of my functions in a try/catch block, and then basically raise an error object until I get to the function that is actually going to handle the error. Is there some sort of 'design method' for error handling that simplifies and centralizes this process? Perhaps something similar to an express-middleware when creating a node server? Or is this a standard way to handle errors in javascript?
Thank you to anyone who can help!
As you are using axios as the http library here, so you can take a look at axios interceptor in order to hook the response and do something with that before passing it to the consumer. This will help you to respond to errors raised from once cental place.
axios.interceptors.response.use((response) => {
return response;
}, function(error) {
// do what you want to do with the error.
return Promise.reject(error)
});
Or with ES5 syntax
axios.interceptors.response.use(function (response) {
// Do something with response data
return response;
}, function (error) {
// Not 200 Ok
// Do something with response error
return Promise.reject(error);
});

Handling unexpected failure from a certain background function in cloud functions firebase

I have a callable initiatePayment function below that process the payment of a user when they pay from client side. This records a new document to my firestore database if the payment is success (addNewRecord) then finally returns payment data from the response of the request.
export const initiatePayment = functions.https.onCall(async (data, context) => {
// destructure data argument
const { userId } = data;
try {
// 1. Process payment via external API request
const payment = await paymentExternalRequest();
const { paymentData, status } = payment.response;
// 2. If payment processing was a success, record new payment data
if (status === "succeeded") {
addNewRecord(userId, paymentData);
}
// 3. Return paymentData to client
return paymentData;
} catch (error) {
throw new functions.https.HttpsError("cancelled", "Cancelled", error);
}
});
addNewRecord function:
const addNewRecord = async (userId, paymentData) => {
const newRecordToAdd = { userId, paymentData };
const docRef = admin
.firestore()
.collection("transactions")
.doc(paymentData.id);
try {
const newRecord = await docRef.set({ userId, transaction: newRecordToAdd });
return newRecord;
} catch (error) {
console.log(error);
}
};
My question is what if addNewRecord fails, how do you handle its error and retry the function again to ensure its success?
You should not have problems with the addNewRecord failing, considering your code. Due to the fact that the function will only be called, based in specific and controlled scenarios, in which you will have the parameters needed for the function to be called correctly, you should be fine.
Anyway, it's very probably that if it failed once, it will fail again, so, you can try to work with a queue system instead of just trying to repeat the execution. This way, you will maintain that data in a queue and run again after checking and handling of the error, to ensure that the addition of the record will occur.
I would recommend you to take a look at the following documents, on queuing with Javascript, that I believe might help you.
Implementation of Queue in Javascript
How would I design a client-side Queue system?
Let me know if the information helped you!

Is creating a new promise with a async function call bad practice?

Snippets are from a node.js and mongoDB CRUD application.Github repo for full code. The code is working fine but unsure if my structure and use of promises and async await are bad practice.
handlers._newbies = {};
handlers._newbies.post = (parsedReq, res) => {
const newbie = JSON.parse(parsedReq.payload);
databaseCalls.create(newbie)
.then((result) => {
res.writeHead(200,{'Content-Type' : 'application/json'});
const resultToString = JSON.stringify(result.ops[0]);
res.write(resultToString);
res.end();
})
.catch(err => console.log(err));
};
const databaseCalls = {};
databaseCalls.create = (newbie) => {
return new Promise(async (resolve, reject) => {
try {
const client = await MongoClient.connect('mongodb://localhost:27017', { useNewUrlParser: true });
console.log("Connected correctly to server");
const db = client.db('Noob-List');
const result = await db.collection('newbies').insertOne(newbie);
client.close();
resolve(result);
} catch(err) {
console.log(err);
}
});
};
When the node server gets a POST request with the JSON payload, it calls the handlers._newbies.post handler which takes the payload and passed it to the
const newbie = JSON.parse(parsedReq.payload);
databaseCalls.create(newbie)
call. I want this database call to return a promise that holds the result of the db.collection('newbies').insertOne(newbie);
call. I was having trouble doing this with just returning the promise returned by the insertOne because after returning I cant call client.close();.
Again maybe what I have done here is fine but I haven't found anything online about creating promises with promises in them. Thank you for your time let me know what is unclear with my question.
It is considered an anti-pattern to be wrapping an existing promise in a manually created promise because there's just no reason to do so and it creates many an opportunities for error, particular in error handling.
And, in your case, you have several error handling issues.
If you get an error anywhere in your database code, you never resolve or reject the promise you are creating. This is a classic problem with the anti-pattern.
If you get an error after opening the DB, you don't close the DB
You don't communicate back an error to the caller.
Here's how you can do your .create() function without the anti-pattern and without the above problems:
databaseCalls.create = async function(newbie) {
let client;
try {
client = await MongoClient.connect('mongodb://localhost:27017', { useNewUrlParser: true });
console.log("Connected correctly to server");
const db = client.db('Noob-List');
return db.collection('newbies').insertOne(newbie);
} catch(err) {
// log error, but still reject the promise
console.log(err);
throw err;
} finally {
// clean up any open database
if (client) {
client.close();
}
}
}
Then, you would use this like:
databaseCalls.create(something).then(result => {
console.log("succeeded");'
}).catch(err => {
console.log(err);
});
FYI, I also modified some other things:
The database connection is closed, even in error conditions
The function returns a promise which is resolved with the result of .insertOne() (if there is a meaningful result there)
If there's an error, the returned promise is rejected with that error
Not particularly relevant to your issue with promises, but you will generally not want to open and close the DB connection on every operation. You can either use one lasting connection or create a pool of connections where you can fetch one from the pool and then put it back in the pool when done (most DBs have that type of feature for server-side work).

Show Apollo mutation error to user in Vue.js?

I am using Vue.js with Vue-Apollo and initiating a User mutation to sign in a user. I am using the graph.cool service.
I have a request pipeline function setup to catch some errors, like an invalid email.
When the request is made with bad / invalid input, my error catch() fires (as expected) and in the network tab I can see the JSON for the custom errors messages. But how do I access these errors / response from within the catch if an error is triggered from graph.cool?
Example:
signin () {
const email = this.email
const password = this.password
this.$apollo.mutate({
mutation: signinMutation,
variables: {
email,
password
}
})
.then((data) => {
// This never fires on an error, so I can't
// show the user the errors in the network repsonse.
console.log(data)
})
.catch((error) => {
// Error in this part fires in the console
// but I'm unable to show the JSON response
// errors because the 'then()' above doesn't execute.
console.error(error)
})
}
I get the following error for an unrecognised user:
Error: GraphQL error: No user found with that information
at new ApolloError (eval at (app.js:956), :34:28)
at eval (eval at (app.js:1353), :139:33)
at
Any idea how to show the errors in the response from within the catch()?
I can literally see the errors I want to show to the user in the response on the network tab here:
...but I can't figure out how to do it.
Any help much appreciated! Thank you.
So, it looks as though I was handling this the wrong way by barking up the wrong tree.
The key to the answer was examining the error from the .catch() with console.dir(error). This revealed some useful keys...namely:
error.graphQLErrors[0]
So all in all, the corrected code looks like this:
signin () {
const email = this.email
const password = this.password
this.$apollo.mutate({
mutation: signinMutation,
variables: {
email,
password
}
})
.then(data => {
console.log(data)
})
.catch(error => {
console.log(graphQLErrorMessages(error))
})
}
The graphQLErrorMessages() function is a helper I wrote, so that I can reuse this in other .catch() blocks:
function graphQLErrorMessages (errorsFromCatch) {
const errors = errorsFromCatch.graphQLErrors[0]
const messages = []
if (errors.hasOwnProperty('functionError')) {
const customErrors = JSON.parse(errors.functionError)
messages.push(...customErrors.errors)
} else {
messages.push(errors.message)
}
return messages
}
It returns an array of error messages (which is what I needed) but you could format this any way you like.
It might be a little https://graph.cool specific in its logic (I'm not so sure), but I hope this ends up helping someone also stuck in a similar situation!
I may be misunderstanding your question so please comment and correct me if I am but it looks like you may be having trouble with Promises more than with Vue or GraphQL.
Just like in a try...catch statement, once you catch an error, your program will continue to execute unless you re-throw the error. For example:
This Catches
try {
codeThatThrowsAnError();
} catch(e) {
// Do Nothing
}
This re-throws
try {
codeThatThrowsAnError();
} catch(e) {
throw new Error("Err 135: I failed")
}
Similarly, in Promise land, you can either catch the error and move like you have in your example, or you can re-throw. What you may be missing is that anything you return from a catch statement will be used in the next then. For example:
somethingReturningAFailedPromise()
.then(doWork)
.catch((err) => {
return "I'm a New Value"
})
.then(console.log)
//=> "I'm a New Value"
It sounds to me like what you need is a data function that is more resilient to failure like the following:
const getUserProfile = (id) => {
return fetchUserData(id)
.catch((err) => {
logError(err);
return {};
})
}

react native fetch not calling then or catch

I am using fetch to make some API calls in react-native, sometimes randomly the fetch does not fire requests to server and my then or except blocks are not called. This happens randomly, I think there might be a race condition or something similar. After failing requests once like this, the requests to same API never get fired till I reload the app. Any ideas how to trace reason behind this. The code I used is below.
const host = liveBaseHost;
const url = `${host}${route}?observer_id=${user._id}`;
let options = Object.assign({
method: verb
}, params
? {
body: JSON.stringify(params)
}
: null);
options.headers = NimbusApi.headers(user)
return fetch(url, options).then(resp => {
let json = resp.json();
if (resp.ok) {
return json
}
return json.then(err => {
throw err
});
}).then(json => json);
Fetch might be throwing an error and you have not added the catch block. Try this:
return fetch(url, options)
.then((resp) => {
if (resp.ok) {
return resp.json()
.then((responseData) => {
return responseData;
});
}
return resp.json()
.then((error) => {
return Promise.reject(error);
});
})
.catch(err => {/* catch the error here */});
Remember that Promises usually have this format:
promise(params)
.then(resp => { /* This callback is called is promise is resolved */ },
cause => {/* This callback is called if primise is rejected */})
.catch(error => { /* This callback is called if an unmanaged error is thrown */ });
I'm using it in this way because I faced the same problem before.
Let me know if it helps to you.
Wrap your fetch in a try-catch:
let res;
try {
res = fetch();
} catch(err) {
console.error('err.message:', err.message);
}
If you are seeing "network failure error" it is either CORS or the really funny one, but it got me in the past, check that you are not in Airplane Mode.
I got stuck into this too, api call is neither going into then nor into catch. Make sure your phone and development code is connected to same Internet network, That worked out for me.

Categories