I was writing a script to pull data from Google Cloud metrics via API when I accidentally discovered that I don't know how to properly catch errors of asynchronous functions. :O
Here is the example code from google cloud:
// Imports the Google Cloud client library
const monitoring = require('#google-cloud/monitoring');
// Creates a client
const client = new monitoring.MetricServiceClient();
/**
* TODO(developer): Uncomment and edit the following lines of code.
*/
const projectId = 'XXXXXXXXX';
async function getMetrics() {
const request = {
name: client.projectPath(projectId),
filter: 'metric.type="cloudsql.googleapis.com/database/cpu/utilization"',
interval: {
startTime: {
// Limit results to the last 20 minutes
seconds: Date.now() / 1000 - 60 * 1,
},
endTime: {
seconds: Date.now() / 1000,
},
},
// Don't return time series data, instead just return information about
// the metrics that match the filter
view: 'HEADERS',
};
// Writes time series data
console.log('start')
const [timeSeries] = await client.listTimeSeries(request);
console.log('Found data points for the following instances:');
timeSeries.forEach(data => {
console.log(data.metric.labels.instance_name);
});
}
getMetrics();
The function listTimeSeries returns a promise. I got an error that I need to be authenticated to perform that action, no problem there.
The issue is that I couldn't catch that error.
I tried surrounding the call with try {...} catch (err) {...} block, wasn't caught.
I tried to catch it like this const [timeSeries] = await client.listTimeSeries(request).catch(console.log); - No luck there.
I must be missing something because I'm pretty new to nodeJS and no way catching errors from async functions is not supported.
I'm using nodeJS v14.
What am I missing guys?
Thank you in advance!
EDIT
As requested (by #CherryDT), here is the full error output:
I hope its not too blurry.
EDIT
It turns out that the way I've been trying to catch errors is fine.
The issue occurred because of listTimeSeries function (from an external library), which threw an error instead of rejecting the promise, which is impossible to catch.
Thanks, guys.👍
Note that I refer to "async functions" and "asynchronous functions." In Javascript "async function" means a function created with the async keyword, whereas when I say "asynchronous function" I mean in the traditional sense, any function that runs asynchronously. In Javascript, functions created with the async keyword are actually just promises under the hood.
Your code would work if errors thrown from asynchronous functions (inside promises) could be caught. Unfortunately, they can't. Unless the function is using the async function syntax, errors in promises must be wrapped with reject. See the MDN example for the gotcha we're looking at here:
// Throwing an error will call the catch method most of the time
var p1 = new Promise(function(resolve, reject) {
throw new Error('Uh-oh!');
});
p1.catch(function(e) {
console.error(e); // "Uh-oh!"
});
// Errors thrown inside asynchronous functions will act like uncaught errors
var p2 = new Promise(function(resolve, reject) {
setTimeout(function() {
throw new Error('Uncaught Exception!');
}, 1000);
});
p2.catch(function(e) {
console.error(e); // This is never called
});
// Errors thrown after resolve is called will be silenced
var p3 = new Promise(function(resolve, reject) {
resolve();
throw new Error('Silenced Exception!');
});
p3.catch(function(e) {
console.error(e); // This is never called
});
I believe this is the code in the library that's throwing the error, below. Notice that another error is being properly rejected. All comments are mine.
for (const methodName of metricServiceStubMethods) {
const callPromise = this.metricServiceStub.then(
stub => (...args: Array<{}>) => {
if (this._terminated) {
// This is the right thing to do!
return Promise.reject('The client has already been closed.');
}
const func = stub[methodName];
return func.apply(stub, args);
},
(err: Error | null | undefined) => () => {
// If this was an async function (as in, using the keyword async,
// not just literally an asynchronous function), this would work,
// because the async keyword is just syntactic sugar for creating
// a promise. But it's not so it can't be caught!
throw err;
}
);
I believe, in this case, unfortunately there's no way for you to catch this error.
You can do this.
(async function() {
try {
await getMetrics();
} catch(error) {
console.log("Error occured:", error);
}
})();
Please note that if you are trying to catch the error in Promise you can use .then(() => { }).catch(err => { }) style, but for async/await you will need try { } catch(err) { } style to catch the error.
Edit
By doing this, it must catch any errors if the promise become rejected. If you still cannot catch the error, this means that the library you're using doesn't reject the promise properly (Promise.reject()), instead it did hard-coded throw error inside the promise instead of rejecting one. For this case you can't do anything with error catching.
Related
I have something like this written in nodejs
const someOtherOperation = async (message) => {
try {
await doSomeIoOperation(message);
} catch (err) {
something
throw Error("Error doing someOtherOperation");
} finally {
await someCleanup();
}
}
const someOperation = async (message) => {
// something else
await someOtherOperation(message);
// something else
}
const main = async () => {
let messagePromises = []
let messages = await getMessages(); // fetching message from a message broker
for (let message of messages) {
messagePromises.push({ id: message.id, promise: someOperation(message) });
}
for (let messagePromise of messagePromises) {
try {
await messagePromise.promise;
} catch (err) {
console.log(err);
}
}
}
The expected behaviour is the for loop with try catch should not end even if there is a error in one of the promises.
What is happening is my process is ending abruptly when i get an error in someotherOperation method , i do not understand i have a try catch at the main loop and any error propagating from the innermost function should be caught in the for loop in main function but it isn't getting caught somehow and the function just ends abruptly
Node.js detection of unhandled rejection is incorrect. There are specific spots in the life cycle of a rejected promise where the engine checks to see if there's a handler and it does not always wait until the last possible moment so it can miss places that we add a handler.
In my code the place where i create a bunch of promises without any error/rejection handlers
messagePromises.push({ id: message.id, promise: someOperation(message) });
when i do the first await on the first promise it return rejected but during this time consider other promises have also been rejected , the Nodejs engine checks if there is a handler for these rejected promises and throws and error if no handler is present. So even though i add a try catch for these promises in a hope to handle the rejections the nodejs engine has already run a check for a handler and not getting one decided to throw a unhandled promise rejection.
To get around my style of code what i did was
const main = async () => {
let messagePromises = []
let messages = await getMessages(); // fetching message from a message broker
for (let message of messages) {
messagePromises.push({ id: message.id,
promise: someOperation(message).then(data=>[data,null]).catch(err=>[null,err]);
}
for (let messagePromise of messagePromises) {
const [data,err] = await messagePromise .promise;
if(err) {
//some error handling code here.
}
}
}
This is a pretty weird behaviour for such a mature language.
I have a function for fetching credentials for an external API (oversimplified):
const fetchCredentials= async () => {
return await fetch(/* url and params */);
};
And another one which calls the above and keeps retrying to call if it the response is not ok.
const retryFetchCredentials = (initialDelay = 250): Promise<Credentials | void> => {
return fetchCredentials().then(async res => {
if (res.ok) {
const parsedResponse = await res.json() as Credentials ;
return parsedResponse;
}
else {
// The issue is with this timeout/return:
setTimeout(() => {
return retryFetchCredentials (initialDelay * 2);
}, initialDelay);
}
});
};
My problem is that I don't know how to strongly type the return inside the setTimeOut function, I keep getting a Promise returned in function argument where a void return was expected. error. I have tried several return types for the functionretryFetchCredentials to no avail.
Any clues about how to solve this?
Just removing the return from the the function inside setTimeout should make the error go away without affecting the behavior of the rest of the code.
As a side note, you shouldn't mix async/await with .then for consistency. And if you use async/await whenever possible, your code is going to increase its readability.
How to handle multiple calls to the same function when its returning nothing. I need to wait untill all calls are finished so i can call another function.
For now I'm using Promise.all() but it doesn't seem right:
Promise.all(table_statements.map(i => insertValues(i)))
.then(function(result) {
readNodeData(session, nodes);
})
.catch(function() {
console.log(err);
})
function insertValues(statement) {
return new Promise((res, rej) => {
database.query(statement, function (err, result) {
if (err) {
rej(err)
}
else{
console.log("Daten in Tabelle geschrieben")
res(); // basically returning nothing
}
});
});
}
This writes data to a database in multiple statements, i need to wait untill all are finished.
Is this actually the "right" way to do it? I mean... it works, but i have the feeling it's not how you are supposed to do it.
Using Promise.all for your case is a good call, since it returns a Promise, when all the promises passed as an iterable are resolved. See the docs.
However, for brevity and readability, try converting your insertValues into async-await function as follows. This tutorial would be a great place to start learning about async functions in JavaScript.
// async insertValues function - for re-usability (and perhaps easy unit testing),
// I've passed the database as an argument to the function
async function insertValues(database, statement) {
try {
await database.query(statement);
} catch (error) {
console.error(error);
}
}
// using the insertValues() function
async function updateDatabase(database) {
try {
// I am using 'await' here to get the resolved value.
// I'm not sure this is the direction you want to take.
const results = await Promise.all(
tableStatements.map(statement => insertValues(database, statement))
);
// do stuff with 'results'.. I'm just going to log them to the console
console.log(results);
} catch (error) {
console.error(error);
}
}
Here, insertValues() function doesn't return any value. Its operation on the database is entirely dependent on the query statement passed to it. I wrapped it within a try-catch block so as to catch any errors that might arise while performing the operation (s) above. More details on handling errors using try-catch can be found here.
Your promisified write to database looks ok, so we can update code from another part.
Let's rewrite it a little to use async/await and try/catch.
(async() => {
const promisifiedStatements = table_statements.map(i => insertValues(i));
try {
await Promise.all(promisifiedStatements);
readNodeData(session, nodes);
} catch(e){
console.log(e)
}
})();
I use here IIFE to use await behaviour.
I am trying to run a small snippet of lambda code where i am pushing data to S3 using firehose. Here is my snippet
const AWS = require( 'aws-sdk' );
var FIREhose = new AWS.Firehose();
exports.handler = async (event,context,callback) => {
// TODO implement
const response = {
statusCode:200,
Name:event.Name,
Value:event.Value
};
const params = {
DeliveryStreamName: 'kinesis-firehose',
Record: { Data: new Buffer(JSON.stringify(response)) }
};
FIREhose.putRecord(params, (err, data) => {
if (err) console.log(err, err.stack); // an error occurred
else console.log(data);
});
};
Here are my events
{
"Name": "Mike",
"Value": "66"
}
When i run this lambda all i am getting response as null . Since i am not passing any callback lambda will default run the implicit callback and returns null. I see that no data is pushed to S3 bucket.
But when i add callback(null,"success") line at the end like this
FIREhose.putRecord(params, (err, data) => {
if (err) console.log(err, err.stack); // an error occurred
else console.log(data);
});
callback(null,"success")
};
I see the data is pushed to S3. Why is that ?
Does async functions always need a callback with some text appended to it ?
Any help is appreciated ?
Thanks
The problem here is that you're mixing your node.js lambda patterns.
Either you use an asynchronous function and return or throw:
exports.handler = async (event,context,callback) => {
// code goes here.
await FIREhose.putRecord(params).promise();
return null; // or whatever result.
};
Or you use the callback approach:
exports.handler = (event,context,callback) => {
// code goes here.
FIREhose.putRecord(params)
.promise();
.then((data) => {
// do stuff with data.
// n.b. you could have used the cb instead of a promise here too.
callback(null, null); // or whatever result.
});
};
(There's a third way using context. but that's a very legacy way).
This is all due to how lambda works and detects when there's been a response.
In your first example (no callback), lambda is expecting your handler to return a promise that it has to wait to resolve/reject, which, in turn, will be the response. However, you're not returning a promise (undefined) and so there's nothing to wait for and it immediately returns- quite probably before the putRecord call has completed.
When you used callback though, you explicitly told lambda that you're using the "old" way. And the interesting thing about the callback approach is that it waits for node's event loop to complete (by default). Which means that .putRecord will probably complete.
I have the following async function that checks the returned value from a promise and I having trouble writing
async function fetchData(pageLocation) {
const data = await
apiService.fetchPage(pageLocation);
if (!data || !data.mapping) {
const error = new Error(`Unknown channel ${pageLocation}`);
error.code = 404;
throw (error);
}
return data.mapping;
}
Test case
describe.only('fetchData', () => {
let fetchPage;
beforeEach(() => {
fetchPage =
sinon.stub().returns(Promise.resolve(mockMapping));
csfPageService.__set__({
apiService: {
fetchPage,
},
});
});
it('should throw an error when there is no available Data', () => {
channeData', async function() {
const fetchChannelSectionData = pageService.__get__('fetchData');
expect(async () => { await fetchData('pageLocation'); }).to.throw();
expect(fetchPage).to.be.calledWith('pageLocation');
console.log('----------------------2');
});
What causing the main issue is having an async function and a promise I am able to use the same approach when it is not an async function and there is no await I have looked into the following links
Catching thrown errors with SinonJS
https://www.chaijs.com/api/bdd/#method_throw
enter link description here
but I haven't been successful
please advise on how should this be done ...
That is one of the reasons I don't like async, await, they are just syntactic sugar over promises, but they uses normal/sync semantics but just in appearance.
Async functions never throws, no matter how bad is the error you throw inside it, they will just return a rejected promise. In your case, your function is not throwing at all, it is returning a rejected promise, and you are not attaching any catch hanlder to that promise, hence the warning. When you use async function or promises, forget about normal handling of errors, promises catches any error automatically and encapsulates them on a rejected promise.
So, in your case the correc way of doing this will vary depending on your testing framework, but it could be something like this:
it('should throw an error when there is no available Data', () => {
channeData', async function() {
const fetchChannelSectionData = pageService.__get__('fetchData');
fetchData('pageLocation').catch(err => {
expect(err).to.be.an.error();
expect(fetchPage).to.be.calledWith('pageLocation');
console.log('----------------------2');
})
});