Is wrapping a promise in a try/catch block the proper method? - javascript

I have a function that calls a promise. On success or failure I would like to return some data beyond what the promise returns.
I thought this might work:
function foo() {
const extra = 'bar'
return thepromise().then((res) => {
return {
result: res,
data: extra
}
}, (err) => {
// this will not happen if an error is thrown in the called promise
return {
result: res,
data: extra
}
})
}
foo().then((res) => { }, (err) => { // error result ends up here })
However this does not work. if an error is thrown in the thepromise it will not call the catch block but instead the catch block of foo().
What is the proper way to handle this? I am successful with a try/catch block but I am not sure this is the best approach:
function foo() {
const extra = 'bar'
return new Promise((resolve, reject) => {
try {
return thepromise(p)
} catch (e) {
reject(e)
}
})
.then(function(res) {
return {
result: res,
data: extra,
status: 'success'
}
}, function(err) {
return {
result: err,
data: extra
status: 'error'
}
})
}

Any function designed to return a promise should not be throwing any sort of exception or error.
If it does, it should be considered buggy.
To fix a buggy method, instead of throwing an exception, reject the returned promise.
Sometimes you're not in a position to fix the underlying bug, such as when you're using someone else's API. If this is the case, the first and most important thing to do is report the bug to the original authors so that the underlying issue can be fixed.
After reporting the issue, you can wrap the buggy method in a simple utility to fix the issue:
function fixBrokenPromise(promise/*, args...*/) {
var args = Array.prototype.slice.call(arguments, 1),
ret;
try {
ret = promise.apply(null, args);
} catch (ex) {
ret = Promise.reject(ex);
}
return ret;
}
This can be called as:
fixBrokenPromise(thepromise/*, args you want to pass to thepromise */)
.then(...resolve...,
...reject...);

If you're willing to entertain extensions to Promises, bluebird has a try method that does just what you need:
import Promise from 'bluebird';
// ...
return Promise
.try(thepromise)
.then(res => ({
result: res,
data: extra
})
.catch(err => ({
result: res,
data: extra
});

The error is considered handled at .catch(), which would return a resolved promise to chained .then(). You can throw the error to the next .catch()
function foo() {
const extra = 'bar'
return thepromise().then((res) => {
return {
result: res,
data: extra
}
}, (err) => {
throw new Error(JSON.stringify({
result: err || "no rejection reason provided",
data: extra
}))
})
}
var thepromise = () => Promise.reject();
foo().then(data => console.log("fulfilled", data))
.catch(err => console.log("catch", JSON.parse(err.message)))

Related

How to call an API twice if there is an error occurred?

I have an internal API that I would like to post data. Depends on some cases, I am seeing errors. So what I would like to do is to call it again if there is an error occurred.
What I did was to create a counter to pass it to the function and call the function recursively as below. This gives me the error as below:
UnhandledPromiseRejectionWarning: Unhandled promise rejection. This error originated either by throwing inside of an async function without a catch block or by rejecting a promise which was not handled with .catch(). (rejection id: 1)
Here is how I call the function:
....
private RETRY_API = 1;
....
try {
await this.callAPI(request, this.RETRY_API);
} catch (error) {
console.log('error', error);
}
This program never comes to the catch block above.
And here is my actual function that I call the API:
private async callAPI(request, retry) {
return new Promise((resolve, reject) => {
someService.postApiRequest('api/url', request, async(err: any, httpCode: number, data) => {
if (this.RETRY_API == 2) {
return reject(err);
} else if (err) {
this.callAPI(request, retry);
this.RETRY_API++;
} else if ( httpCode !== 200 ) {
this.RETRY_API = 2;
// some stuff
} else {
this.RETRY_API = 2;
// some stuff
return resolve(data);
}
});
})
}
Not sure what I am missing. If there is a better way to call the API twice if an error occurred, that would be great if you let me know.
Let's organize a little differently. First, a promise-wrapper for the api...
private async callAPI(request) {
return new Promise((resolve, reject) => {
someService.postApiRequest('api/url', request,(err: any, httpCode: number, data) => {
err ? reject(err) : resolve(data);
});
});
}
A utility function to use setTimeout with a promise...
async function delay(t) {
return new Promise(resolve => setTimeout(resolve, t));
}
Now, a function that calls and retries with delay...
private async callAPIWithRetry(request, retryCount=2, retryDelay=2000) {
try {
return await callAPI(request);
} catch (error) {
if (retryCount <= 0) throw err;
await delay(retryDelay);
return callAPIWithRetry(request, retryCount-1, retryDelay);
}
}
If you can't force a failure on the api to test the error path some other way, you can at least try this...
private async callAPIWithRetry(request, retryCount=2, retryDelay=2000) {
try {
// I hate to do this, but the only way I can test the error path is to change the code here to throw an error
// return await callAPI(request);
await delay(500);
throw("mock error");
} catch (error) {
if (retryCount <= 0) throw err;
await delay(retryDelay);
return callAPIWithRetry(request, retryCount-1, retryDelay);
}
}
It looks like you need to add return await to the beginning of the line this.callAPI(request, retry); in callAPI function.
Similarly there are some condition blocks that doesn't resolve or reject the promise. While it might work okay, it's considered bad practice. You want to either resolve or reject a promise.
I've accomplished calling an API a second time when I received an error by using axios' interceptors functions.
Here is a code snippet you can review:
axios.interceptors.response.use(
// function called on a successful response 2xx
function (response) {
return response;
},
// function called on an error response ( not 2xx )
async function (error) {
const request = error.config as AxiosRequestConfig;
// request is original API call
// change something about the call and try again
// request.headers['Authorization'] = `Bearer DIFFERENT_TOKEN`;
// return axios(request)
// or Call a different API
// const new_data = await axios.get(...).then(...)
// return new_data
// all else fails return the original error
return Promise.reject(error)
}
);
Try replacing
if (this.RETRY_API == 2)
with
if (this.RETRY_API > 1)

Why is my Promise always hitting the catch() method?

I'm using the JavaScript Promise object with a then(), catch().
The console.log in the catch() method always runs, regardless of the response from the API ("STATUS_SUCCESS" or "STATUS_FAILED").
Is this normal behaviour in promises or is there a way to only hit the catch() method if the response has failed?
Updated with live example:
sendAccountDataToBackend(response) {
const { formData } = response;
const requestObj = {
url: 'http://localhost:3000/api/validate',
data: {
firstname: 'dummy_firstname',
lastname: 'dummy_lastname',
email: 'dummyemail'
}
};
let p = new Promise((resolve, reject) => {
account.Utils.globalAjaxRequest(requestObj, (success) => {
if(success.status === 'STATUS_SUCCESS') {
resolve();
console.log('resolved: ', p)
} else {
reject();
console.log('rejected: ', p);
}
});
})
p.then(() => {
console.log('Then: ', response);
}).catch(() => {
console.log('catch:', response);
})
}
You can find the exact cause of the thrown error by printing it.
Change your catch handler to look like this:
catch((e) => {
console.log('Catch', e);
})
In addition to "Catch" you will see a description of the error in the console.
I figured out what was causing the catch to fire thanks to #pfcodes suggestion. I was calling a function within the then() block which was failing. Once removed, it stayed inside then(). Silly mistake that was over looked! Thanks for your suggestions.
Regarding the question:
Is this normal behaviour in promises or is there a way to only hit the catch() method if the response has failed?
It is easy to demonstrate that the catch is only entered when the reject call is made.
function testPromise(shouldReject)
{
return new Promise((resolve, reject) => {
setTimeout(function(){
if(shouldReject) reject();
else resolve();
},1000);
});
}
function handlePromise(p, name){
p.then(() => {
console.log(name, 'Then');
}).catch(() => {
console.log(name, 'Catch');
})
}
var rejectPromise = testPromise(true);
var resolvePromise = testPromise(false);
handlePromise(rejectPromise,"reject");
handlePromise(resolvePromise,"resolve");
So, what this means is that in your code, for whatever reason (probably badly handled asynchronous ajax call) you're entering the else block.
Regarding your update, I would say that success.status is returning something other than "STATUS_SUCCESS" for the reasons demonstrated by the code above.
Try adding console.log(success) inside the ajax callback.
account.Utils.globalAjaxRequest(requestObj, (success) => {
console.log(success);
....

Am I chaining Promises correctly or committing a sin?

I have not worked with Javascript in a long time, so now promises are a new concept to me. I have some operations requiring more than one asynchronous call but which I want to treat as a transaction where steps do not execute if the step before failed. Currently I chain promises by nesting and I want to return a promise to the caller.
After reading the chaining section of Mozilla's Using Promises guide, I'm not sure if what I'm doing is correct or equivalent to the "callback pyramid of doom".
Is there a cleaner way to do this (besides chaining with a guard check in each then)? Am I right in my belief that in Mozilla's example it will execute each chained then even when there is an error?
myfunction(key) => {
return new Promise((outerResolve, outerReject) => {
return new Promise((resolve, reject) => {
let item = cache.get(key);
if (item) {
resolve(item);
} else {
//we didnt have the row cached, load it from store
chrome.storage.sync.get(key, function (result) {
chrome.runtime.lastError
? reject({ error: chrome.runtime.lastError.message })
: resolve(result);
});
}
}).then((resolve) => {
//Now the inner most item is resolved, we are working in the 'outer' shell
if (resolve.error) {
outerReject(resolve);
} else {
//No error, continue
new Promise((resolve, reject) => {
chrome.storage.sync.get(keyBasedOnPreviousData, function (result) {
chrome.runtime.lastError
? reject({ error: chrome.runtime.lastError.message })
: resolve(result);
});
}).then((resolve) => {
//finally return the result to the caller
if (resolve.error) {
outerReject(resolve);
} else {
outerResolve(resolve);
}
});
}
});
});
}
Subsequent then statements are not executed (until a catch) when an exception is thrown. Also, .then returns a Promise, so you don't need to create an additional, outer Promise.
Try this example:
var p = new Promise((resolve, reject) => {
console.log('first promise, resolves');
resolve();
})
.then(() => {
throw new Error('Something failed');
})
.then(() => {
console.log('then after the error');
return('result');
});
p.then(res => console.log('success: ' + res), err => console.log('error: ' + err));
You will not see "then after the error" in the console, because that happens after an exception is thrown. But if you comment the throw statement, you will get the result you expect in the Promise.
I am not sure I understand your example entirely, but I think it could be simplified like this:
myfunction(key) => {
return new Promise((resolve, reject) => {
let item = cache.get(key);
if (item) {
resolve(item);
} else {
//we didnt have the row cached, load it from store
chrome.storage.sync.get(key, function (result) {
chrome.runtime.lastError
? throw new Error(chrome.runtime.lastError.message)
: resolve(result);
});
}
}).then((previousData) => {
// keyBasedOnPreviousData is calculated based on previousData
chrome.storage.sync.get(keyBasedOnPreviousData, function (result) {
chrome.runtime.lastError
? throw new Error(chrome.runtime.lastError.message)
: return result;
});
});
}
It's a bit of a mess. This is my attempt at rewriting. A good thing to try to avoid is new Promise().
function chromeStorageGet(key) {
return new Promise( (res, rej) => {
chrome.storage.sync.get(key, result => {
if (chrome.runtime.lastError) {
rej(new Error(chrome.runtime.lastError.message))
} else {
res(result)
}
});
});
});
function myfunction(key) {
const item = cache.get(key) ? Promise.resolve(cache.get(key)) : chromeStorageGet(key);
return item.then( cacheResult => {
return chromeStorageGet(keyBasedOnPreviousData);
});
}
Why avoid new Promise()?
The reason for this is that you want to do every step with then(). If any error happened in any of the promises, every promise in the chain will fail and any subsequent then() will not get executed until there is a catch() handler.
Lots of promise based-code requires no error handlers, because promise-based functions always return promises and exceptions should flow all the back to the caller until there is something useful to be done with error handling.
Note that the exceptions to these 2 rules are in my chromeStorageGet function. A few notes here:
new Promise can be a quick and easy way to convert callback code to promise code.
It's usually a good idea to just create a little conversion layer for this callback-based code. If you need chrome.storage.sync in other places, maybe create a little utility that promisifies all its functions.
If there is only 1 'flow', you can just use a series of then() to complete the process, but sometimes you need to conditionally do other things. Just splitting up these complicated operations in a number of different functions can really help here.
But this:
const result = condition ? Promise.resolve() : Promise.reject();
Is almost always preferred to:
const result = new Promise( (res, rej) => {
if (condition) {
res();
} else {
rej();
}
}

What am i doing wrong with Promise rejections here?

So I've made a bit of reusable code here for node, and I'm applying it via async / await. Albeit I'm sure I am misunderstanding a lot here when working with this... But, I swear, I have one project I'm using this code that it works, and another where it doesn't.
Im using request and request-promise.
UrlRequest: function( opts ) {
return new Promise( (resolve, reject) => {
request( opts,
function(error, request, body) {
if (error)
reject( {error: true, msg: error} );
else
resolve( {body, request} );
});
})
.catch(err => reject( {error: true, msg: err} ));
}
I am fairly sure the .catch() is wrong. But it didn't error out in my 1st project. So i'm trying to figure out the proper way of doing this. The few articles I've looked through is where I came up with this function for usage. I also know if any error actually happens ( this case included ), it will throw a UnhandledPromiseRejectionWarning error. So how is this properly handled?
How I use it:
(async () => {
var result = await Promise.UrlRequest( {
url: "...",
method: "GET",
headers: DefaultHeaders
} );
// do stuff with result...
}) ();
Since you already installed request-promise, you don't need constructing the Promise as you are doing. Simply use the it instead of request then you would have a promise returned. Something similar to this should work:
const request = require('request-promise')
request(opts)
.then((res) => {
// Process res...
})
.catch((err) => {
// Handle error...
});
You can proceed to wrap it in your UrlRequest function and use with async as follows:
UrlRequest: async ( opts ) => {
try {
const response = await request(opts);
return response;
} catch (error) {
// Handle error
}
}
In the case that you want to use then() and catch(), you can do this:
UrlRequest: ( opts ) => {
return request(opts)
.then(response => response)
.catch (error) {
// Handle error
}
}
With request-promise, you don't need to write your own Promise wrapper
// make sure you're using the promise version
const request = require('request-promise')
var opts = {
...
resolveWithFullResponse: true // <--- <--- to get full response, response.body contains the body
};
// if you dont plan to use UrlRequest as constructor, better name is starting with lowercase: urlRequest, some naming convention
UrlRequest: async function( opts ) {
let res;
try {
res = await request(opts);
} catch (e) {
// handle error
throw e
}
return res;
}
Note: async function wraps the return in Promise

Returning one object that's built with nested promises

I'm struggling to wrap my head around a nested promise layout where one one object is returned at the end of it. My current code is as follows:
router
router.get(`/${config.version}/event/:id?`, function (req, res, next) {
var event = new Event(req, res, next);
event.getInfo(req.params.id).then((info) => {
res.send(info);
});
});
function
getInfo(id) {
db.main('events').where('id', id).select()
.then((result) => {
if(result.length > 0) {
var event = result[0];
//regular functions
event.status = this.getStatus(id);
event.content = this.getContent(id);
event.price = this.getPrice(id);
//promise functions
var users = this.getUsers(id);
var hosts = this.getHosts(id);
Promise.all([users, hosts]).then(values => {
event.users = values[0];
event.hosts = values[1];
//return whole event object to router
return event;
})
.catch((err) => {
return {
result: 'error',
error: err
};
});
} else {
return {
result: 'error',
error: "Event does not exist"
};
}
}).catch((e) => {
return {
result: 'error',
error: "Could not retrieve event info"
};
});
}
As you can see, the router initiates a call to get info about an event. The function then does a database call and gets some event data. Thereafter I need to get the users and hosts of the event from a different table, append that info to the event object as well and then return the whole object to the router to be sent to the client.
When I do this I get an error because I'm not returning a promise from the getInfo function, but I'm not sure how or which promise I'm supposed to return.
I'd appreciate some help with this. Thanks
using .then means that you are returning a promise.
function getInfo(id) {
return new Promise(function(resolve, reject) {
resolve('yay!');
})
}
getInfo().then(function(result) { //result = yay! });
to make your code work, simply replace all the returns with resolves, the errors with rejects, and wrap the whole thing with a return new Promise as i did.
getInfo(id) {
return new Promise(function(resolve, reject) {
db.main('events').where('id', id).select()
.then((result) => {
if (result.length > 0) {
var event = result[0];
//regular functions
event.status = this.getStatus(id);
event.content = this.getContent(id);
event.price = this.getPrice(id);
//promise functions
var users = this.getUsers(id);
var hosts = this.getHosts(id);
Promise.all([users, hosts]).then(values => {
event.users = values[0];
event.hosts = values[1];
//return whole event object to router
resolve(event);
})
.catch((err) => {
reject({
result: 'error',
error: err
});
});
} else {
reject({
result: 'error',
error: "Event does not exist"
});
}
}).catch((e) => {
reject({
result: 'error',
error: "Could not retrieve event info"
});
});
});
}
Just wrap your async code in Promise like this:
getInfo(id) {
return new Promise(function(resolve, reject) {
db.main('events').where('id', id).select()
.then((result) => {
//...
resolve(/* result */)
// OR
reject(/* Error */)
})
}
Note: Use resolve and reject instead return
It's a combination of several things, but the main one is that you are never returning anything from getInfo, so your router handler is calling .then on undefined.
Do not call .catch (without throwing inside it) on Promises you intend to return for a caller to consume. This makes it not possible to use .catch, because you recovered the Promise chain into a resolved one.
Whatever you return inside a .then will be merged into the promise chain, so it's not actually a "Promise that resolves with a Promise". Your whole code could be replaced with:
getInfo (id) {
return db.main('events').where('id', id).select()
.then(result => {
if (result.length == 0) {
// you can also just throw your error object thing,
// but standard Error are generally the convention
throw new Error('Event does not exist')
}
const [event] = result
event.status = this.getStatus(id)
event.content = this.getContent(id)
event.price = this.getPrice(id)
return Promise.all([this.getUsers(id), this.getHosts(id)])
.then(([users, hosts]) => {
event.users = users
event.hosts = hosts
// this is the only value that
// this.getInfo(id).then(value => {/* ... */}) will see
return event
}
})
}

Categories

Resources