I am learning how to use class with Async/Await. I think I am doing something wrong with getData function in the Run class.
It should output "Hello World" when using await get() (experiment).
When I run the script, I get an error:
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)
Class Script:
class Run {
constructor() {
this.stop = false;
}
async getData(entry) {
if (this.stop) {
console.log("Stopped")
return;
}
return await this.get();
}
async get() {
return "Hello World";
}
stop() {
this.stop = true;
}
}
Usage:
let run = new Run();
run.getData(async (entry) => {
console.log(entry);
});
You're getting an error because you forgot the this qualifier:
async getData(entry) {
if (this.stop) {
^^^^
Using return await only makes sense, when you use it within a try/catch block. Otherwise, it is completely redundant.
You should use it here instead. Also, getData does not use its parameter entry. You should call it directly:
console.log(await run.getData());
^^^^^
Your original code refer to the other methods, not the one in your class:
async getData(entry) {
if (stop) { // <--- this refers to this.stop() method
console.log("Stopped")
return;
}
return await get(); <--- this doesn't refer to your `this.get()` method
}
So, add this. to fix it at above two positions.
Related
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.
Comment: I'm sure this is quite simple, but I can't seem to figure out the right combination of async/await try/catch.
Senario: I'm reading DHT22 temp/humidity sensor that may return an error, in which case I want to return a default value. I want getHumidity() to wait for reading and return value or default value. And then printConditions() simple calls and doesn't execute until it receives a response.
Question: Is it possible have delay in getHumidity(), and other calls are unaware its async, cause I have a lot of variations of printConditions()?
const printConditions = () => `Current Humidity is: ${getHumidity().fixed(2)}`;
//Both Attempts return: Current Humidity is NaN%
//Which I believe implies it is not waiting nor default value of 75.0.
//Attempt 1
const getHumidity = async () => {
try { return await sensor.read(22, sensorPin).humidity; }
catch (error) {console.log(error); return 75.0; }
}
try/catch block returns this error: ??? : (node:1368) 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)
//Attempt 2:
const getHumidity = async () => {
return await sensor.read(22, sensorPin)
.then(value=>{ return value.humidity;})
.catch(error=>{console.log(error); return 75.0;});
}
const printConditions = async () => {
let reading;
try {
reading = await sensor.read(22, sensorPin).humidity;
}
catch(error) {
console.log(error);
reading = 75.0;
}
finally {
console.log(`Current Humidity is: ${reading.fixed(2)}`);
}
}
did you try adding an await when you call the asynchronus function?
const printConditions = async () => `Current Humidity is: ${await getHumidity().fixed(2)}`;
I am hard time writing test to assert something happened inside catch block which is executed inside forEach loop.
Prod code
function doSomething(givenResourceMap) {
givenResourceMap.forEach(async (resourceUrl) => {
try {
await axios.delete(resourceUrl);
} catch (error) {
logger.error(`Error on deleting resource ${resourceUrl}`);
logger.error(error);
throw error;
}
});
I am wanting to assert logger.error is being called twice and called with right arguments each time. So I wrote some test like this
describe('Do Something', () => {
it('should log message if fail to delete the resource', function() {
const resource1Url = chance.url();
const givenResourceMap = new Map();
const thrownError = new Error('Failed to delete');
givenResourceMap.set(resource1Url);
sinon.stub(logger, 'error');
sinon.stub(axios, 'delete').withArgs(resource1Url).rejects(thrownError);
await doSomething(givenResourceMap);
expect(logger.error).to.have.callCount(2);
expect(logger.error.getCall(0).args[0]).to.equal(`Error deleting resource ${resource1Url}`);
expect(logger.error.getCall(1).args[0]).to.equal(thrownError);
// Also need to know how to assert about `throw error;` line
});
});
I am using Mocha, sinon-chai, expect tests. Above test is failing saying logger.error is being 0 times.
Thanks.
The problem is that you are using await on a function that doesn't return a Promise. Note that doSomething is not async and does not return a Promise object.
The forEach function is async but that means they'll return right away with an unresolved Promise and you don't ever await on them.
In reality, doSomething will return before the work inside of the forEach is complete, which is probably not what you intended. To do that you could use a regular for-loop like this:
async function doSomething(givenResourceMap) {
for (const resourceUrl of givenResourceMap) {
try {
await axios.delete(resourceUrl);
} catch (error) {
logger.error(`Error on deleting resource ${resourceUrl}`);
logger.error(error);
throw error;
}
}
}
Note that it changes the return type of doSomething to be a Promise object rather than just returning undefined as it originally did. But it does let you do an await on it as you want to in the test (and presumably in production code also).
However since you re-throw the exception caught in the loop, your test will exit abnormally. The test code would have to also change to catch the expected error:
it('should log message if fail to delete the resource', function(done) {
// ... the setup stuff you had before...
await doSomething(givenResourceMap).catch(err => {
expect(logger.error).to.have.callCount(2);
expect(logger.error.getCall(0).args[0]).to.equal(`Error deleting resource ${resource1Url}`);
expect(logger.error.getCall(1).args[0]).to.equal(thrownError);
done();
});
});
I launch my first promise and wait for it (this is the very high-level process of my system, it's where I chain everything together):
install_demo_component.then(), then, I need to handle the response from that:
install_demo_component.then(response => {
if(response.failed) {
undo_changes_we_did_from_install.then(response => {
if(response.failed) {
completely_uninstall_demo_component.then(response => {
if(response.failed) {
throw BIG_ERROR;
}
}).catch(error => {
});
}
}).catch(error => {
});
}
}).catch(error => {
});
But I can't avoid it. Nor can I make it prettier, me thinks. The way these promises are structured to wait for each other or start a new promise chain if response.failed arises is essential to how my system works, it's related to the business logic.
I think a lot of people would see this as callback hell and although I think callback hell in essence is something else, I agree it doesn't look nice.
If it really is callback hell, what's the alternative?
Original Answer
That isn't really promise chaining. To chain promises, you need to return a promise from the .then() method. I would rewrite your code like this:
install_demo_component.then(response => {
if(response.failed) return undo_changes_we_did_from_install
return Promise.reject();
}).then(response => {
if(response.failed) return completely_uninstall_demo_component
return Promise.reject();
}).then(response => {
if(response.failed) throw BIG_ERROR;
return Promise.reject();
}).catch(error => {
//You only need one catch when promise chaining.
});
returning Promise.reject(); will break out of the promise chain and jump to .catch()
Click here for more info on promise chaining.
Error handling with promises
My original response was how to convert your code to a promise chain without changing it too much. I believe that's the best way to help people understand what was changed. However, I do have a suggestion on how you can use promises to their fullest.
Instead of checking for response.failed, you can have your asynchronous functions such as undo_changes_we_did_from_install reject on failure. This will remove the need for all of the conditional statements.
//The function below will either resolve() or reject()
install_demo_component.catch(response => { //Notice only .catch() is used.
return undo_changes_we_did_from_install
}).catch(response => {
return completely_uninstall_demo_component
}).catch(response => {
throw BIG_ERROR;
};
You see .catch() is already invoked conditionally, the condition is if the previous promise rejected instead of resolved. And you see that the .catch() method can also be chained.
For a short article on this go here.
To avoid callback hell, all you have to do is run those install test inside of an async function. This can be done inside of an IIFE:
// returns a Promise
function install_demo_component() {
return new Promise((resolve, reject) => {
resolve({
failed: false,
func: 'install_demo_component'
});
});
}
// returns a Promise
function undo_changes_we_did_from_install() {
return new Promise((resolve, reject) => {
resolve({
failed: true,
func: 'undo_changes_we_did_from_install'
});
});
}
// returns a Promise
function completely_uninstall_demo_component() {
return new Promise((resolve, reject) => {
resolve({
failed: true,
func: 'completely_uninstall_demo_component'
});
});
}
// run install tests inside of async IIFE (Immediately Invoked Function Expression)
(async function() {
// try/catch is used instead of the .catch() method when using async/await
try {
// using the new `...` from ES6 inside of an obj litteral, properties can be accessed from the function
if({ ...await install_demo_component() }.failed) {
console.log('install failed');
} else if({ ...await undo_changes_we_did_from_install()}.failed ) {
console.log('undo install changes failed');
} else if({ ...await completely_uninstall_demo_component() }.failed) {
console.log('completely uninstall demo component failed!!!!, What do we do?!!!!!');
}
} catch(err) {
console.log(err);
}
}());
EDIT
Made the if/else logic make more sense based off of the order of actions needing to take place:
// run install tests inside of async IIFE (Immediately Invoked Function Expression)
(async function() {
// try/catch is used instead of the .catch() method when using async/await
try {
// using the new `...` from ES6 inside of an obj litteral, properties can be accessed from the function
if(!{ ...await install_demo_component() }.failed) {
console.log('install succeed');
} else if(!{ ...await undo_changes_we_did_from_install() }.failed) {
console.log('undo install changes succeed');
} else if(!{ ...await completely_uninstall_demo_component() }.failed) {
console.log('completely uninstall demo component succeed');
} else {
console.log('Everything failed and now this sucker is gaana blow');
}
} catch(err) {
console.log(err);
}
async await does wonders to get rid of all the trailing });
async function ur_func() {
let response = await install_demo_component;
if (response.failed) {
let undo_response = await undo_changes_we_did_from_install
if (undo_response.failed) {
let uninstall_response = completely_uninstall_demo_component;
if (uninstall_response) {
throw BIG_ERROR;
}
}
}
}
ur_func();
You could also use early returns to get rid of the indentation, but that's more a matter of preference.
I have a series of API calls I need to make from a 'user profile' page on my app. I need to prioritize or order the calls when I land on the component.
I have tried using async-await on the componentDidMount lifecycle method but when the first call fails, the rest do not get called.
...
async componentDidMount() {
await user.getGameStats();
await user.getFriendsList();
await user.getPlayStyle();
}
...
Despite ordering the calls, I would like them to still execute regardless of whether the preceding calls failed.
You need to account for rejected promises. If you don't catch the error it will stop execution. Just add a catch() block to each function that may fail.
function a(){
return new Promise((r,f)=>{
console.log('a');
r();
});
}
function b(){
return new Promise((r,f)=>{
console.log('b');
f(); // rejecting this promise
});
}
function c(){
return new Promise((r,f)=>{
console.log('c');
r();
});
}
async function d(){
throw new Error('Something broke');
}
(async ()=>{
await a().catch(()=>console.log('caught a'));
await b().catch(()=>console.log('caught b'));
await c().catch(()=>console.log('caught c'));
await d().catch(()=>console.log('caught d'));
})();
It's a dirty solution, but you can do something like this:
user.getGameStats().then({res => {
callGetFriendsList();
}).catch({err =>
callGetFriendsList();
});
function callGetFriendsList() {
user.getFriendsList().then(res => {
user.getPlayStyle();
}).catch(err => {
user.getPlayStyle();
});
}
The ideal and good way would be to call all of them at the same time asynchronously if they are not dependent on the response of the previous request.
Just add an empty catch at the end of each API call as following
async componentDidMount() {
await user.getGameStats().catch(err=>{});
await user.getFriendsList().catch(err=>{});
await user.getPlayStyle().catch(err=>{});
}
I'd catch to null:
const nullOnErr = promise => promise.catch(() => null);
async componentDidMount() {
const [gameStats, friendsList, playStyle] = await Promise.all([
nullOnErr(user.getGameStats()),
nullOnErr(user.getFriendsList()),
nullOnErr(user.getPlayStyle())
]);
//...
}
I also used Promise.all to run the calls in parallel as there seems to be no dependency between the calls.