Proper way to handle fetch errors in NodeJS v18? - node.js

I switched to NodeJS v18 with the built-in fetch and I'm using it as such:
async function get511AK() {
let res = await fetch(URL, { method: 'GET' })
if (res.ok && (res.headers.get('content-type').includes('json'))) {
let data = await res.json();
jsonresponseAK = data;
} else {
console.log("(" + res.url + ') is not json');
}
}
However, sometimes I'm getting a timeout on the URL, which is going to happen, but it's causing the script to exit. I've tried wrapping this in try/catch and it did not prevent it from exiting.
This never happened in Node v12 under the node-fetch library. What else can I add to control those connection timeouts?
node:internal/deps/undici/undici:11118
Error.captureStackTrace(err, this);
^
TypeError: fetch failed
at Object.fetch (node:internal/deps/undici/undici:11118:11)
at process.processTicksAndRejections (node:internal/process/task_queues:95:5)
at async Timeout.get511AK [as _onTimeout] (/home/wazebot/dot-scripts/script-relay.js:76:12) {
cause: ConnectTimeoutError: Connect Timeout Error
at onConnectTimeout (node:internal/deps/undici/undici:6625:28)
at node:internal/deps/undici/undici:6583:50
at Immediate._onImmediate (node:internal/deps/undici/undici:6614:13)
at process.processImmediate (node:internal/timers:471:21) {
code: 'UND_ERR_CONNECT_TIMEOUT'
}
}
Node.js v18.12.1

Hope it helped!
process.on('uncaughtException', console.log);
// Uncaught Exception thrown - when you throw an error and did not catch anywhere.
process.on('unhandledRejection', console.log);
// Unhandled Rejection at Promise - similar, when you fail to catch a Promise.reject.

Related

Why does delaying handling of promise rejections sometimes result in UnhandledPromiseRejectionWarning?

When I run the following code, why do I get unhandled promise rejection warning?
async function load() {
throw new Error('error');
}
async function main() {
const promise = load();
await new Promise(resolve => setTimeout(resolve, 5000));
try {
await promise;
} catch (e) {
console.log('caught error', e);
}
}
main();
This is the output:
jkim#dev-jkim test $ node index.js
(node:25276) UnhandledPromiseRejectionWarning: Error: error
Since await promise is around a try-catch, I'm confused why my try-catch isn't catching the error. I guess it's something to do with the setTimeout since the following code works:
async function load() {
throw new Error('error');
}
async function main() {
const promise = load();
try {
await promise;
} catch (e) {
console.log('caught error', e);
}
}
main();
jkim#dev-jkim test $ node index.js
caught error Error: error
What is going on here? If promise rejections are not handled by the end of the current tick, does it automatically result in a unhandled promise rejection warning?
(I'm on node v10.16.3)
If promise rejections are not handled by the end of the current tick, does it automatically result in a unhandled promise rejection warning?
Yes. A Promise must have a rejection handler attached to it at the moment it rejects, or the rejection will count as unhandled. If you attach the rejection handler later, such as after a
await new Promise(resolve => setTimeout(resolve, 5000));
the load Promise has rejected by the time the interpreter gets to the
try {
await promise;
} catch (e) {
so, although the rejection can be caught with .catch, it wasn't caught by anything at the moment of rejection, resulting in the warning.
Best practice for this sort of thing is to always attach a rejection handler immediately - whether that means .catch, or inside a try/catch, or a Promise.all, or returning the Promise for the caller to handle.
The function load() throws an Error. When an error is thrown while a Promise is being handled, the Promise is rejected. Now, if the Error load() threw is not caught then, a UnhandledPromiseRejectionWarning is thrown by JS
A better Illustration of your code is:
function load() {
console.log("hello after some time");
}
const foo = new Promise((resolve, reject) => {
setTimeout(resolve, 5000);
})
.then(() => { load(); })
.catch((e) => { console.log(`Caught Error: ${e}`)});

creating custom token in cloud function after reteiving data from realtime database

i want to create custom token in cloud function but before that I want to check and compare timestamp from realtime database and with current time.if the timestamp is below 10 min then I want to create custom token and send back to client.please help me to achieve this.i am new to this cloud function firebase.
here is my code
export const authaccount = functions.https.onCall(async (data) => {
try {
const snap= await admin.database().ref("/register/"+data).get();
const time=snap.val().timestamp;
const now=new Date().getDate();
const reg=new Date(time).getDate();
const today=Math.abs(now-reg);
const daydiff=Math.floor(today/1000/60/60/24);
const nowminutes=new Date().getUTCMinutes();
const regminutes=new Date(time).getUTCMinutes();
const timediff=Math.abs(nowminutes-regminutes);
if (timediff<10 && daydiff==0) {
try {
admin.auth().createCustomToken(data).then((customtoken)=>{
console.log("auth created"+" "+timediff+" "+daydiff+" "+customtoken);
return customtoken;
});
} catch (err1) {
throw new functions.https.HttpsError("unknown", err1.message, err1);
}
} else {
console.log("else "+" "+now+" "+reg+" "+time+" "+daydiff);
}
} catch (err2) {
throw new functions.https.HttpsError("unknown", err2.message, err2);
}
});
2:53:20.626 AM
authaccount
Error: Process exited with code 16 at process.<anonymous> (/layers/google.nodejs.functions-framework/functions-framework/node_modules/#google-cloud/functions-framework/build/src/invoker.js:275:22) at process.emit (events.js:314:20) at process.EventEmitter.emit (domain.js:483:12) at process.exit (internal/process/per_thread.js:168:15) at Object.sendCrashResponse (/layers/google.nodejs.functions-framework/functions-framework/node_modules/#google-cloud/functions-framework/build/src/logger.js:37:9) at process.<anonymous> (/layers/google.nodejs.functions-framework/functions-framework/node_modules/#google-cloud/functions-framework/build/src/invoker.js:271:22) at process.emit (events.js:314:20) at process.EventEmitter.emit (domain.js:483:12) at processPromiseRejections (internal/process/promises.js:209:33) at processTicksAndRejections (internal/process/task_queues.js:98:32)
2:53:19.559 AM
authaccount
Error: The caller does not have permission; Please refer to https://firebase.google.com/docs/auth/admin/create-custom-tokens for more details on how to use and troubleshoot this feature. at FirebaseAuthError.FirebaseError [as constructor] (/workspace/node_modules/firebase-admin/lib/utils/error.js:44:28) at FirebaseAuthError.PrefixedFirebaseError [as constructor] (/workspace/node_modules/firebase-admin/lib/utils/error.js:90:28) at new FirebaseAuthError (/workspace/node_modules/firebase-admin/lib/utils/error.js:149:16) at Function.FirebaseAuthError.fromServerError (/workspace/node_modules/firebase-admin/lib/utils/error.js:188:16) at /workspace/node_modules/firebase-admin/lib/auth/token-generator.js:114:53 at processTicksAndRejections (internal/process/task_queues.js:97:5) at async Promise.all (index 1)
2:53:19.558 AM
authaccount
Unhandled rejection
2:53:19.469 AM
authaccount
Function execution took 1386 ms, finished with status code: 200
please help to solve this problem. i don't know where I am making the mistake.
You will need to make sure your Firebase Admin sdk is initiated and running before the function proceeds
if (firebase.apps.length === 0) {
firebase.initializeApp();
}
Resource: https://firebase.google.com/docs/admin/setup#initialize-without-parameters
I doubt you have modified the IAM permissions on your service account but as the comment suggested: https://firebase.google.com/docs/auth/admin/create-custom-tokens#service_account_does_not_have_required_permissions
Once that is confirmed to be working - you will need to ensure that the onCall data is a string and not null, some simple health checks can help you debug your process
console.log(typeof data);
console.warn("Data", data);
from there I would also debug your date times and the realtime database result, these are async and will require the promise to be resolved before you can use it.
Update:
All cloud functions should return a response to the client
onCall uses promises on the client and supports a 'return Object'
example:
return {
token: myCustomToken,
possible: otherValue
};
for comparison, onRequest uses fetch like responses and supports codes
response.status(500)
response.send({name:value})
return;
Source:
https://firebase.google.com/docs/functions/callable#sending_back_the_result
Source:
https://firebase.google.com/docs/functions/http-events#using_express_request_and_response_objects
Update:
all paths and promises need to resolve correctly, this includes awaiting promises to resolve and returning their result or storing the result for any secondary processing - I suggest cleaning up the code, remove the try/catch and use .then().catch()
Example:
if (timediff<10 && daydiff==0) {
return await admin.auth().createCustomToken(data)
.then((customtoken)=>{
console.log("auth created"+" "+timediff+" "+daydiff+" "+customtoken);
return customtoken;
})
.catch (err) {
return new functions.https.HttpsError("unknown", err.message, err);
}
}
else {
console.log("else "+" "+now+" "+reg+" "+time+" "+daydiff);
return "else "+" "+now+" "+reg+" "+time+" "+daydiff;
}

Try to cach UnhandledPromiseRejectionWarning discord.js

i'm trying to catch an discord.js error
This error pops up when internet is off, but i want some clean code instead this messy one...
How can i catch this?
I did really try everything..
code:
(node:11052) UnhandledPromiseRejectionWarning: Error: getaddrinfo ENOTFOUND disc
ordapp.com
at GetAddrInfoReqWrap.onlookup [as oncomplete] (dns.js:66:26)
(Use `node --trace-warnings ...` to show where the warning was created)
(node:11052) 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(). To termin
ate the node process on unhandled promise rejection, use the CLI flag `--unhandl
ed-rejections=strict` (see https://nodejs.org/api/cli.html#cli_unhandled_rejecti
ons_mode). (rejection id: 2)
(node:11052) [DEP0018] DeprecationWarning: Unhandled promise rejections are depr
ecated. In the future, promise rejections that are not handled will terminate th
e Node.js process with a non-zero exit code.
i did try this at the very top :
process.on('uncaughtException', function (err) {
//console.log('### BIG ONE (%s)', err);
console.log("555")
});
aswell this one :
client.on('error', error => {
if (error.code === 'ENOTFOUND') {
console.log(no internet!!)
}
});
I also did try this to see where its from, but nothing shows up its still the same
try {
var err = new Error("my error");
Error.stackTraceLimit = infinity;
throw err;
} catch(e) {
console.log("Error stack trace limit: ")
console.log(Error.stackTraceLimit);
}
Error stack trace limit:
10
(node:11008) UnhandledPromiseRejectionWarning: Error: getaddrinfo ENOTFOUND disc
ordapp.com
here is the code i use for now what gives the error.
i just want to catch the error in something like this: (No connection)
const Discord = require('discord.js')
const client = new Discord.Client({ autoReconnect: true });
const opn = require('opn')
const getJSON = require('get-json')
const request = require('request');
const config = require("./config/config.json");
const pushbullet = require("./config/pushbullet.json");
const addons = require("./config/addons.json");
const Registration = require("./config/Reg.json");
client.on('uncaughtException', function (err) {
//console.log('### BIG ONE (%s)', err);
console.log("555")
});
client.login(config.Settings[0].bot_secret_token);
I would try to wrap it with try/catch.
And maybe add the following code to understand better what is happening.
Error.stackTraceLimit = Infinity
Reference:
https://developer.mozilla.org/en-US/docs/Archive/Web/JavaScript/Microsoft_Extensions/Error.stackTraceLimit
Remember to remove it after the problem solved, this is not suitable for production use.
Well i solved it!!
I put this on top and its all solved.
process.on('unhandledRejection', error => {
if (error.code === "ENOTFOUND") { console.log ("No internet connection")}
if (error.code === "ECONNREFUSED") { console.log ("Connection refused")}
//console.log('Unhandled promise rejection:', error.code);
});

How to catch UnhandledPromiseRejectionWarning for GCS WriteStream

Observed Application Behavior
I'm getting a UnhandledPromiseRejectionWarning: Error: Upload failed when using #google-cloud/storage in node.js.
These errors come when processing thousands of requests. It's a small percentage that cause errors, but due to the lack of ability to handle the errors, and the lack of proper context from the error message, it's very difficult to determine WHICH files are failing.
I know in general promises must have a .catch or be surrounded by a try/catch block. But in this case I'm using a write stream. I'm a little bit confused as to where the promise that's being rejected is actually located and how I would intercept it. The stack trace is unhelpful, as it only contains library code:
UnhandledPromiseRejectionWarning: Error: Upload failed
at Request.requestStream.on.resp (.../node_modules/gcs-resumable-upload/build/src/index.js:163:34)
at emitTwo (events.js:131:20)
at Request.emit (events.js:214:7)
at Request.<anonymous> (.../node_modules/request/request.js:1161:10)
at emitOne (events.js:121:20)
at Request.emit (events.js:211:7)
at IncomingMessage.<anonymous> (.../node_modules/request/request.js:1083:12)
at Object.onceWrapper (events.js:313:30)
at emitNone (events.js:111:20)
at IncomingMessage.emit (events.js:208:7)
My Code
The code that's creating the writeStream looks like this:
const {join} = require('path')
const {Storage} = require('#google-cloud/storage')
module.exports = (config) => {
const storage = new Storage({
projectId: config.gcloud.project,
keyFilename: config.gcloud.auth_file
})
return {
getBucketWS(path, contentType) {
const {bucket, path_prefix} = config.gcloud
// add path_prefix if we have one
if (path_prefix) {
path = join(path_prefix, path)
}
let setup = storage.bucket(bucket).file(path)
let opts = {}
if (contentType) {
opts = {
contentType,
metadata: {contentType}
}
}
const stream = setup.createWriteStream(opts)
stream._bucket = bucket
stream._path = path
return stream
}
}
}
And the consuming code looks like this:
const gcs = require('./gcs-helper.js')
module.exports = ({writePath, contentType, item}, done) => {
let ws = gcs.getBucketWS(writePath, contentType)
ws.on('error', (err) => {
err.message = `Could not open gs://${ws._bucket}/${ws._path}: ${err.message}`
done(err)
})
ws.on('finish', () => {
done(null, {
path: writePath,
item
})
})
ws.write(item)
ws.end()
}
Given that I'm already listening for the error event on the stream, I don't see what else I can do here. There isn't a promise happening at the level of #google-cloud/storage that I'm consuming.
Digging into the #google-cloud/storage Library
The first line of the stack trace brings us to a code block in the gcs-resumable-upload node module that looks like this:
requestStream.on('complete', resp => {
if (resp.statusCode < 200 || resp.statusCode > 299) {
this.destroy(new Error('Upload failed'));
return;
}
this.emit('metadata', resp.body);
this.deleteConfig();
this.uncork();
});
This is passing the error to the destroy method on the stream. The stream is being created by the #google-cloud/common project's utility module, and this is using the duplexify node module to create the stream. The destroy method is defined on the duplexify stream and can be found in the README documentation.
Reading the duplexify code, I see that it first checks this._ondrain before emitting an error. Maybe I can provide a callback to avoid this error being unhandled?
I tried ws.write(item, null, cb) and still got the same UnhandledPromiseRejectionWarning. I tried ws.end(item, null, cb) and even wrapped the .end call in a try catch, and ended up getting this error which crashed the process entirely:
events.js:183
throw er; // Unhandled 'error' event
^
Error: The uploaded data did not match the data from the server. As a precaution, the file has been deleted. To be sure the content is the same, you should try uploading the file again.
at delete (.../node_modules/#google-cloud/storage/build/src/file.js:1295:35)
at Util.handleResp (.../node_modules/#google-cloud/common/build/src/util.js:123:9)
at retryRequest (.../node_modules/#google-cloud/common/build/src/util.js:404:22)
at onResponse (.../node_modules/retry-request/index.js:200:7)
at .../node_modules/teeny-request/build/src/index.js:208:17
at <anonymous>
at process._tickCallback (internal/process/next_tick.js:189:7)
My final code looks something like this:
let ws = gcs.getBucketWS(writePath, contentType)
const handleErr = (err) => {
if (err) err.message = `Could not open gs://${ws._bucket}/${ws._path}: ${err.message}`
done(err)
}
ws.on('error', handleErr)
// trying to do everything we can to handle these errors
// for some reason we still get UnhandledPromiseRejectionWarning
try {
ws.write(item, null, err => {
handleErr(err)
})
ws.end()
} catch (e) {
handleErr(e)
}
Conclusion
It's still a mystery to me how a user of the #google-cloud/storage library, or duplexify for that matter, is supposed to perform proper error handling. Comments from library maintainers of either project would be appreciated. Thanks!

How to test a function that throws an error asynchronously, using tape?

I am attempting to test this module (receiver.js) for an error thrown:
var request = require('request')
module.exports = function(url){
request({
url: url,
method: 'POST'
}, function(error) {
if(error){
throw error
}
})
}
using this test (test.js):
var test = require('tape')
test('Receiver test', function(t){
var receiver = require('./receiver')
t.throws(function(){
receiver('http://localhost:9999') // dummy url
}, Error, 'Should throw error with invalid URL')
t.end()
})
but tape runs the assertion before the error is thrown, resulting in the following error message:
TAP version 13
# Receiver test
not ok 1 Should throw error with invalid URL
---
operator: throws
expected: |-
[Function: Error]
actual: |-
undefined
at: Test.<anonymous> (/path/to/tape-async-error-test/test.js:5:4)
...
/path/to/receiver.js:9
throw error
^
Error: connect ECONNREFUSED 127.0.0.1:9999
at Object.exports._errnoException (util.js:856:11)
at exports._exceptionWithHostPort (util.js:879:20)
at TCPConnectWrap.afterConnect [as oncomplete] (net.js:1062:14)
Is there a way around this?
Generally, using tape, you have to ensure you call assert.end() after the async call has completed. Using promises (would require request-promise and returning the promise):
test('Receiver test', function(t){
// Tells tape to expec a single assertion
t.plan(1);
receiver('http://localhost:9999')
.then(() => {
t.fail('request should not succeed')
})
.catch(err => {
t.ok(err, 'Got expected error');
})
.finally({
t.end();
});
});
Using async/await:
test('Receiver test', async function(t) {
try {
await receiver('http://localhost:9999');
assert.fail('Should not get here');
} catch (err) {
assert.ok(err, 'Got expected error');
}
t.end();
});
The above example is mostly correct but here's a complete working example that compares async to synchronous side by side and also shows how to check for the error message in a manner similar to the tape examples given on tape's README.md.
test('ensure async function can be tested to throw', async function(t) {
// t.throw works synchronously
function normalThrower() {
throw(new Error('an artificial synchronous error'));
};
t.throws(function () { normalThrower() }, /artificial/, 'should be able to test that a normal function throws an artificial error');
// you have to do this for async functions, you can't just insert async into t.throws
async function asyncThrower() {
throw(new Error('an artificial asynchronous error'));
};
try {
await asyncThrower();
t.fail('async thrower did not throw');
} catch (e) {
t.match(e.message,/asynchronous/, 'asynchronous error was thrown');
};
});

Resources