I am using await to make the code cleaner, but I am not sure whether I am handling exceptions correctly.
An example while using azure-devops-node-api;
const foo = async() => {
return new Promise((resolve, reject) => {
...
...
const teams = await coreApiObject.getTeams(currProject.id)
.catch(err => { reject(err) return })
...
...
})
}
In this code I am assuming, if there is a problem with promise call, foo() is going to return reject.
async functions always return a promise, so you don't need to explicitly create one yourself. Any non-promise value returned from an async function is implicitly wrapped in a promise.
Inside the foo function, you just need to await the call coreApiObject.getTeams(...) and to catch and handle any error, use the try-catch block.
Your code can be simplified as shown below:
const foo = async() => {
try {
const teams = await coreApiObject.getTeams(currProject.id);
return teams;
} catch (e) {
// handle error
}
}
If you want to the calling code to handle the error, then you can use one of the following options:
Remove the try-catch block and just return the result of coreApiObject.getTeams(...).
const foo = async() => {
return coreApiObject.getTeams(currProject.id);
}
Removing the try-catch block and just returning the call to coreApiObject.getTeams(...) will allow the calling code to handle the error because the promise returned by the foo function will get resolved to the promise returned by coreApiObject.getTeams(...); this means that the fate of the promise returned by the foo function will depend on whatever happens to the promise returned by coreApiObject.getTeams(...).
If the promise returned by coreApiObject.getTeams(...) is rejected, promise returned by the foo function will also be rejected and hence the calling code will have a change to catch the promise rejection and handle it.
Throw the error from the catch block.
const foo = async() => {
try {
const teams = await coreApiObject.getTeams(currProject.id);
return teams;
} catch (error) {
// throw the error
throw error;
}
}
Other option is to throw the error from the catch block to make sure that the promise returned by the async function is rejected.
If you don't throw the error or return a promise or a thenable that is rejected, returning any other value from the catch block will fulfil the promise returned by the async function with whatever value is returned inside the catch block.
Related
I have a store method...
async store() {
try {
return await axios.post('/upload', data);
} catch (error) {
}
},
Called by:
store().then(()=>{ console.log('ok'); }, ()=>{ console.log('not ok'); });
But when the store method fails and an error is caught, the first method in then is always called, how can I get the failed not ok method to be called?
You need to throw the error caught in the catch block of the store function
async store() {
try {
return await axios.post('/upload', data);
} catch (error) {
throw error;
}
}
You could also skip catching the error in the store function and simply catch it when store function is called. To do this, you just need to return the result of axios.post(...).
async store() {
return axios.post('/upload', data);
}
(Note that, without the try-catch block, you don't need an await before axios.post(...) because the promise returned by the store function will be resolved to the promise returned by axios.post(...). This means that if the promise returned by axios.post(...) is fulfilled, promise returned by the store function will also fulfil with the same fulfilment value with which the promise returned by axios.post(...) fulfilled.)
It is uncommon to pass second argument to then function. Instead, you should chain a .catch() block to catch the error.
store()
.then(() => console.log('ok'))
.catch(() => console.log('not ok'));
The following is valid:
new Promise<void>((resolve, reject) => {
reject()
})
.then(() => {})
.catch(() => {})
But I might not always care about the error. Is there a way to make the catch optional?
I tried this but it didn't work:
new Promise<void>((resolve, reject?) => {
if (reject) reject()
})
.then(() => {})
Error: Uncaught (in promise): undefined
Is there a way to make the catch optional?
No. If you are using a promise that might error, you need to handle that (or propagate it to your caller).
Of course if you create the promise yourself, rejecting it is optional, and you can choose to never reject your promises so that you won't need to handle any errors. But if there are errors from promises that you are using, and you want to ignore them, you must do so explicitly. Just write
somePromise.catch(e => void e);
// or () => { /* ignore */ }
// or function ignore() {}
I was trying to solve the same issue, and finally come up with the following promise wrapper:
/**
* wraps a given promise in a new promise with a default onRejected function,
* that handles the promise rejection if not other onRejected handler is provided.
*
* #param customPromise Promise to wrap
* #param defaultOnRejected Default onRejected function
* #returns wrapped promise
*/
export function promiseWithDefaultOnRejected(customPromise: Promise<any>, defaultOnRejected: (_: any) => any): Promise<any> {
let hasCatch = false;
function chain(promise: Promise<any>) {
const newPromise: Promise<any> = new Promise((res, rej) => {
return promise.then(
res,
function(value) {
if (hasCatch) {
rej(value);
} else {
defaultOnRejected(value);
}
},
);
});
const originalThen = newPromise.then;
// Using `defineProperty` to not overwrite `Promise.prototype.then`
Object.defineProperty(newPromise, 'then', {
value: function (onfulfilled: any, onrejected: any) {
const result: Promise<any> = originalThen.call(newPromise, onfulfilled, onrejected);
if (typeof onrejected === 'function') {
hasCatch = true;
return result;
} else {
return chain(result);
}
}
});
return newPromise;
}
return chain(customPromise);
}
This function lets you wrap your promises with a defaultOnRejected function that will handle the rejected promise if no other handler is provided. For example:
const dontCare = promiseWithDefaultOnRejected(Promise.reject("ignored"), () => {});
The result promise will never throw an "Unhandled Promise Rejection", and you can use it as follows:
dontCare.then(x=>console.log("never happens")).catch(x=>console.log("happens"));
or
dontCare.then(x=>console.log("never happens"), x=>console.log("happens"));
or simply without onRejected handler:
dontCare.then(x=>console.log("never happens")).then(x=>console.log("also never happens"));
An issue with this util is that it is not working as expected with async/await syntax: you need to propagate and handle the "catch" path as follows:
async () => {
try {
await promiseWithDefaultOnRejected(Promise.reject("ignored"), () => {})
.catch((e) => { throw e; });
} catch (e) {
console.log("happens");
}
}
You could resolve when the error is something you don't care about. If your catch returns anything other than a rejected promise, the error isn't propagated down the chain.
const ignorableError = new Error("I don't care about this error");
const myPromise = new Promise((resolve, reject) => {
reject(ignorableError);
})
.then(() => {})
.catch(error => {
if(error == ignorableError) {
console.log("Error ignored");
return;
}
// Do something else...
});
myPromise.then(() => console.log("Success"))
Let me try to describe your situation:
You have a service that gets user information and a function called getUser that uses that service. When the service fails for any reason then getUser does not have a user available. The result of getUser is used quite a lot of times in your code with the following situation(s):
User is available run a function (block of code).
User is not available run a function (block of code).
Run a function with the error/reject of the service.
When using getUser result you may want to run all 3 functions, a combination of 2 of them or only one.
Current getUser returns a Promise and this type does not seem to be suitable for your situation. Mainly because rejecting a promise and not catching it will cause unhandled promise rejection. And because if you want to run code if user is available or not it complicates the functions (they both have to check the result instead of assuming user is or is not available).
Maybe you can try the following code, please be careful making assumptions in the not available block, this could be due to any error. For example: it does not mean the user does not exist because it could be a network error.
In the example getUser is used but can be any function that returns a promise where you assume not available on reject.
const isAvailable = promise => {
//do not expose NOT_AVAILABLE outside this function
const NOT_AVAILABLE = {type:"not available"};
//no uncaught promise rejection errors by VM
const savePromise = promise.catch(x=>x);
return {
available:fn=>
promise
.catch(e=>Promise.reject(NOT_AVAILABLE))
.then(fn)
.catch(
e=>
(e===NOT_AVAILABLE)
? undefined//ignore
: Promise.reject(e)//re throw, error is not from service
),
//call not available with the promise if promise rejects
notAvailable:fn=>promise.catch(()=>fn(promise)),
catchError:promise.catch.bind(promise)
};
}
const service = arg =>
(arg===1)
? Promise.resolve(arg)
: Promise.reject(arg)
const getUser = arg => isAvailable(service(arg));
var user = getUser(2);
//if service failed available will be skipped
user.available(
user=>console.log("skipped:",user)
);
//both catch and notAvailable will be called
user.notAvailable(
arg=>console.log("not available:",arg)
);
user.notAvailable(
arg=>console.log("still not available:",arg)
);
//not handling catchError does not cause uncaught promise exception
// but you can inspect the error
// user.catchError(
// err=>console.log("error is::",err)
// );
var user = getUser(1);
//you can call available on user multiple times
user.available(
user=>console.log("got user:",user)
);
user.available(
user=>Promise.resolve(user)
.then(user=>console.log("still got user:",user))
.then(x=>Promise.reject("have to catch this one though"))
.catch(e=>console.log("ok, got error trying to run available block:",e))
);
//showing you can inspect the error
var user = getUser(5);
user.catchError(err=>console.log("error from service:",err));
Hi everyone!
I use Node.js v8.5.0.
And this is my code in Node.js (simplified):
// function that returns promise
const request = (url) =>
new Promise((resolve, reject) => {
superagent
.get(url)
.end((err, res) => {
if (err || !res.ok) {
reject(err);
return;
}
resolve(res);
})
})
// function that add .then and .catch to promise
const to = promise =>
promise
.then((data) => [null, data])
.catch((err) => [err]);
// making request
let [err, result] = await to(request());
When i do request and some error occurs .catch function doesnt catch rejected value and i get error like Unhandled Promise Rejection. But, in fact I added .catch function to promise.
Does anyone know what is wrong here?
Thanks for help!
A superagent request already returns a promise so use that instead
const request = (url) => superagent.get(url).then(res=> {/*transform res**/ return res})
The code you provided does not have that behavior but I am familiar with the uncaught in promise warning.
If you want to catch an error of a promise later you have to catch it first and return the promise before you catch it.
The following will give you uncaught in promise warning:
var failPromise = val => Promise.reject(val);
var to = async promise => promise.then(val=>[null,val]).catch(err=>[err]);
var p = failPromise(88);
//later you will catch the error
setTimeout(()=>to(p).then(val=>console.log("resolved to:",val)),100);
But if you change it a bit then you won't
var failPromise = val => {
const p = Promise.reject(val);
p.catch(ignore=>ignore);
return p;//return the promise that did not have the catch
};
var to = async promise => promise.then(val=>[null,val]).catch(err=>[err]);
var p = failPromise(88);
//later you will catch the error
setTimeout(()=>to(p).then(val=>console.log("resolved to:",val)),100);
As suggested before; your request method should just return the promise that superagent returns but in such a way that you can catch the reject later without the errors(warnings):
const request = (url) => {
const p = superagent
.get(url);
p.catch(ignore=>ignore);
return p;
})
If you are wondering why you get uncaught in promise; it is because you catch the rejection in the queue and not in the stack. Stack and queue is explained here.
The example that catches in stack causes no errors in the console but then returns the promise that did not have the catch on it. Depending how far in the queue you are going to catch it it may still give you errors but in code provided it won't.
I have this async function below:
async LoadEditForm() {
const { default : EditHero } = await import('./EditHero');
this.setState({ lazyEditHero: EditHero })
}
And it is called it here:
handleEnableAddMode = async () => {
await this.LoadEditForm();
this.setState({
addingHero: true,
selectedHero: { id: '', name: '', saying: '' }
});
}
I have a question regarding the code above:
At this line: await this.LoadEditForm(); in the handleEnableAddMode function, does it block that function? In other words does the this.setState(...) function get called immediately or does it wait for the await call to finish.
Since I heard people saying the async/await concept is to allow you to write async code in a synchronous manner. That is kind of confusing me. Can someone clarify?
At this line: await this.LoadEditForm(); in the handleEnableAddMode function, does it block that function? In other words does the this.setState(...) function get called immediately or does it wait for the await call to finish.
One of those is not the other one "in other words".
It doesn't block, it waits. The control will return to the caller of the async function. When the promise being waited on is resolved or rejected, then the waiting function will resume.
https://jsfiddle.net/7yxhqoo4/ has a simple example, thus:
function resolveAfterHalfSecond() {
return new Promise(resolve => {
setTimeout(() => {
resolve();
}, 500);
});
}
async function f1() {
$("div").append("<p>before await</p>");
await resolveAfterHalfSecond();
$("div").append("<p>after await</p>");
}
function test()
{
$("div").append("<p>before call async function</p>");
f1();
$("div").append("<p>after call async function</p>");
}
$(test);
The immediate output looks like:
before call async function
before await
after call async function
And after half a second, it looks like
before call async function
before await
after call async function
after await.
When the await was hit in f1() it didn't block, but immediately went back to test() and finished that function. When the promise was resolved, f1() resumed.
Typically await will hold until it receives either a successful result because the promise was "resolved", or it encounters an error because the promise was "rejected".
If neither of these two events occur, your promise is broken and the await may never complete.
When you're calling an Asyc function, it will return a promise, whenever you return a value in your async function your promise will be resolved, and if you throw an exception, your promise is rejected. On the other side, the await expression pauses the execution of the async function and waits for the passed Promise's resolution, and then resumes the async function's execution and returns the resolved value.
Based on the provided samples in Mozile documentations:
function getProcessedData(url) {
return downloadData(url) // returns a promise
.catch(e => {
return downloadFallbackData(url) // returns a promise
})
.then(v => {
return processDataInWorker(v); // returns a promise
});
}
async function getProcessedData(url) {
let v;
try {
v = await downloadData(url);
} catch(e) {
v = await downloadFallbackData(url);
}
return processDataInWorker(v);
}
In your case, your setState won't be executed until your await this.LoadEditForm(); be fulfilled. But the thing is you've not catch the probable rejection of your promise. So if the function b rejected, your setState will be executed anyway.
So you have to wrap the await function and the following lines in a try ... catch statement.
Consider this function:
aPromise = require('axios');
function middleware(callback) {
axios.get('/api/get')
.then(callback)
.catch(callback);
}
Consider this test:
const callback = (err) => {
expect(isError(err)).toBe(true);
done();
};
middleware(callback);
The isError is a lodash function.
Consider aPromise as something I want to test. If the promise always resolves, this test should not pass. But it will! And that's because the promise's catch actually catches the expect exception.
My question is: How to not catch the error in a promise's catch handler when expect throws an error in the promise's then handler?
Note that I don't use async/await.
You need to create a failed promise and you need to return the promise in your test. Please have a look on the doc on testing promises.
aPromise = require('axios');
jest.mock('axios', () => {
get: ()=> jest.fn() //initialy mock the get function
})
it('catch failing promises',() = > {
const result = Promise.reject('someError'); //create a rejected promises
aPromise.get.mockImplementation(() => result)// let `get` return the rejected promise
const callback = jest.fn()
middleware(callback)
return result
.then (()=>{
expect(callback).toHaveBeenCalledWith('someError');
})
})