Node-Fetch unhandled promise rejection (can't catch it) - javascript

I'm struggling for 2 days on this one so I try asking here:
I have a nodejs script using node-fetch to make queries ton an API. However, I've tried adding catch blocks everywhere but it seems I didn't suceeded in ahndling the error.
Here is my function:
fetchUrl.get = async (url, params = false, headers = false) => {
let defaultOptions = {
method: "GET",
};
let finalUrl = new URL(url);
Object.keys(params).forEach((key) =>
finalUrl.searchParams.append(key, params[key])
);
let finalHeaders = {};
if (headers != null && headers != false && Object.keys(headers).length > 0) {
Object.keys(headers).forEach((headerKey) => {
finalHeaders[headerKey] = headers[headerKey];
});
}
defaultOptions["headers"] = finalHeaders;
let result = null;
try {
result = await fetch(finalUrl, defaultOptions)
.then((res) => {
if (res.status == 200) {
return {
success: true,
text: res.text(),
};
} else {
console.log("ERROR during Fetch: " + res.status);
console.error(finalUrl);
console.error(params);
res.text().then((err) => {
console.error(err);
});
return {
success: false,
};
}
})
.then((resParsed) => {
if (resParsed.success) {
return resParsed.text;
} else {
return false;
}
});
} catch (err) {
throw err;
}
return result;
};
And this is the error I get:
(node:2272) UnhandledPromiseRejectionWarning: FetchError: request to http://gdsfdfs.com/ failed, reason: getaddrinfo ENOTFOUND gdsfdfs.com
at ClientRequest.<anonymous> (file:///home/kashnars/pearlv2/node_modules/node-fetch/src/index.js:108:11)
at ClientRequest.emit (events.js:375:28)
at Socket.socketErrorListener (_http_client.js:475:9)
at Socket.emit (events.js:375:28)
at emitErrorNT (internal/streams/destroy.js:106:8)
at emitErrorCloseNT (internal/streams/destroy.js:74:3)
at processTicksAndRejections (internal/process/task_queues.js:82:21)
(node:2272) 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 terminate the node process on unhandled promise rejection, use the CLI flag `--unhandled-rejections=strict` (see https://nodejs.org/api/cli.html#cli_unhandled_rejections_mode). (rejection id: 50)
I know the URL is wrong, it's just for testing purposes. I need to catch the error.
Someone having the solution?
Thanks in advance,

The issue is that you throw an error, which is a Promise rejection in an async function
The calling function needs to handle the rejection
Now, since all you do in the catch is rethrow the error, no need to use try/catch at all
More simplifiction of your code is possible - but, basically, from the await it should go like
const res = await fetch(finalUrl, defaultOptions);
if (res.status == 200) {
return res.text();
}
console.log("ERROR during Fetch: " + res.status);
console.log(finalUrl);
console.log(params);
const err = await res.text();
console.error(err);
return false;
with no try/catch
Alternatively, if you want this function to handle the network error, and return false, just like when status !== 200
try {
const res = await fetch(finalUrl, defaultOptions);
if (res.status == 200) {
return res.text();
}
console.log("ERROR during Fetch: " + res.status);
console.log(finalUrl);
console.log(params);
const err = await res.text();
throw err;
} catch(err) {
console.error(err);
return false;
}
as a bonus, the code above the try { or await fetch can be simplified to
fetchUrl.get = async (url, params = false, headers = false) => {
const defaultOptions = {
method: "GET",
headers: {...(headers ?? {}) }
};
const finalUrl = new URL(url);
Object.entries(params ?? {}).forEach(([key, value]) => {
finalUrl.searchParams.append(key, value)
});

Related

catching exception thrown by service worker message event

I can't catch an exception thrown by the service worker's message event..
The client uses following code to execute the command on the SW:
import { messageSW } from "workbox-window";
// .. code for Workbox initialization/registration omitted
messageSW(registration?.active, { type: "SYNC" })
.then((results) => {
console.log("done");
})
.catch((e) => {
console.error(e);
});
On the SW (sw.js) side I have the following code:
self.addEventListener("message", async (event) => {
if (requestType === "SYNC") {
event.ports[0].postMessage(await longRunningTask());
}
});
This solution works OK as long as the SW is not throwing any exceptions. Meaning that the client prints the "done" message after the long running process on the SW is executed. If the exception is thrown nothing gets returned, ever.
I have managed to fix the problem by doing the following:
self.addEventListener("message", async (event) => {
if (requestType === "SYNC") {
try {
event.ports[0].postMessage(await longRunningTask());
} catch (error) {
event.ports[0].postMessage(error);
}
}
});
In this case - the result is always returned regardless, "done" is printed, but:
how do I actually produce an exception from the service worker, so the client could catch and handle it?
In general it would be good to hear if what I am doing is an appropriate approach to how asynchronous code on the SW shall be invoked from the client...
Here is my own solution I ended up using:
On service worker side - helper method:
async function replyToSenderAsync(event, task) {
let isCanReply = event.ports && event.ports.length >= 0;
try {
const result = await task();
if (isCanReply) {
event.ports[0].postMessage({ error: null, message: result });
}
} catch (error) {
if (isCanReply) {
event.ports[0].postMessage({ error: error, message: null });
}
}
}
When exception is caught we set the error property. Use as:
self.addEventListener("message", async (event) => {
const requestType = event?.data?.type;
if (requestType === "QUEUE_CLEAR") {
await replyToSenderAsync(event, async () => await clearQueueAsync());
}
});
On client side request wrapper:
function sendMessageToSWAsync(targetSW, messageType, message) {
return new Promise(function (resolve, reject) {
if (
!isServiceWorkerSupported.value ||
!isServiceWorkerRegistered.value ||
!targetSW
) {
reject(new Error("Unable to send the message to a service worker"));
}
try {
messageSW(targetSW, { type: messageType, message: message })
.then((messageResponse) => {
if (!messageResponse) {
reject(new Error("Service worker responsed with empty response"));
} else {
if (messageResponse.error) {
reject(messageResponse.error);
} else {
resolve(messageResponse.message);
}
}
})
.catch((messageError) => {
reject(messageError);
});
} catch (error) {
reject(error);
}
});
}
The magic here is to read the error property and reject the promise if that is the case (hence causing an exception to be thrown). Use as
try {
let response = await sendMessageToSWAsync(registration?.active, "QUEUE_GET_ALL");
}
catch(error) {
}
sendMessageToSWAsync(registration?.active, "QUEUE_GET_ALL")
.then((response) => {})
.catch((error) => {})

Non-Error promise rejection captured with value: undefined

const validatePnr = async (pnr: string) => {
return new Promise<void>(async (resolve, reject) => {
try {
setIsLoading(true);
const url = `/api/v2/pnr/validate?stationCode=${stationCode}&outletId=${outletId}&vendorId=${vendorId}&pnr=${pnr}${
!!currentStationInfo?.arrival ? `&eta=${currentStationInfo.arrival}` : ""
}`;
const data = (await CaptchaGet({ url, disableErrorToast: true })) as ApiResponse<ValidatePnrInfo>;
if (data.status === "failure") {
invalidPnrCb(data.message, data.result);
reject();
} else if (data.result.status && data.result.statusCode === 200) {
const pnrResponse: PnrResponse = { status: data.status, result: data.result.data, message: data.message };
validPnrCb(pnrResponse, pnr);
resolve();
} else {
invalidPnrCb(data.result.statusMsg, data.result);
reject();
}
setData(data.result);
} catch (err) {
setIsLoading(false);
invalidPnrCb("Something went wrong. Please try again.");
reject();
}
});
};
Getting Non-Error promise rejection captured with value: undefined in JavaScript promise handling sentry, I am handling rejection properly but need your suggestion where should I change in code?
Pass something in reject('something went wrong'));
It's empty (undefined) that's why you see that error.

Fetch API POST in React app not logging errors in catch block from express/node server [duplicate]

Here's what I have going:
import 'whatwg-fetch';
function fetchVehicle(id) {
return dispatch => {
return dispatch({
type: 'FETCH_VEHICLE',
payload: fetch(`http://swapi.co/api/vehicles/${id}/`)
.then(status)
.then(res => res.json())
.catch(error => {
throw(error);
})
});
};
}
function status(res) {
if (!res.ok) {
return Promise.reject()
}
return res;
}
EDIT: The promise doesn't get rejected, that's what I'm trying to figure out.
I'm using this fetch polyfill in Redux with redux-promise-middleware.
Fetch promises only reject with a TypeError when a network error occurs. Since 4xx and 5xx responses aren't network errors, there's nothing to catch. You'll need to throw an error yourself to use Promise#catch.
A fetch Response conveniently supplies an ok , which tells you whether the request succeeded. Something like this should do the trick:
fetch(url).then((response) => {
if (response.ok) {
return response.json();
}
throw new Error('Something went wrong');
})
.then((responseJson) => {
// Do something with the response
})
.catch((error) => {
console.log(error)
});
The following login with username and password example shows how to:
Check response.ok
reject if not OK, instead of throw an error
Further process any error hints from server, e.g. validation issues
login() {
const url = "https://example.com/api/users/login";
const headers = {
Accept: "application/json",
"Content-Type": "application/json",
};
fetch(url, {
method: "POST",
headers,
body: JSON.stringify({
email: this.username,
password: this.password,
}),
})
.then((response) => {
// 1. check response.ok
if (response.ok) {
return response.json();
}
return Promise.reject(response); // 2. reject instead of throw
})
.then((json) => {
// all good, token is ready
this.store.commit("token", json.access_token);
})
.catch((response) => {
console.log(response.status, response.statusText);
// 3. get error messages, if any
response.json().then((json: any) => {
console.log(json);
})
});
},
Thanks for the help everyone, rejecting the promise in .catch() solved my issue:
export function fetchVehicle(id) {
return dispatch => {
return dispatch({
type: 'FETCH_VEHICLE',
payload: fetch(`http://swapi.co/api/vehicles/${id}/`)
.then(status)
.then(res => res.json())
.catch(error => {
return Promise.reject()
})
});
};
}
function status(res) {
if (!res.ok) {
throw new Error(res.statusText);
}
return res;
}
For me,
fny answers really got it all. since fetch is not throwing error, we need to throw/handle the error ourselves.
Posting my solution with async/await. I think it's more strait forward and readable
Solution 1: Not throwing an error, handle the error ourselves
async _fetch(request) {
const fetchResult = await fetch(request); //Making the req
const result = await fetchResult.json(); // parsing the response
if (fetchResult.ok) {
return result; // return success object
}
const responseError = {
type: 'Error',
message: result.message || 'Something went wrong',
data: result.data || '',
code: result.code || '',
};
const error = new Error();
error.info = responseError;
return (error);
}
Here if we getting an error, we are building an error object, plain JS object and returning it, the con is that we need to handle it outside.
How to use:
const userSaved = await apiCall(data); // calling fetch
if (userSaved instanceof Error) {
debug.log('Failed saving user', userSaved); // handle error
return;
}
debug.log('Success saving user', userSaved); // handle success
Solution 2: Throwing an error, using try/catch
async _fetch(request) {
const fetchResult = await fetch(request);
const result = await fetchResult.json();
if (fetchResult.ok) {
return result;
}
const responseError = {
type: 'Error',
message: result.message || 'Something went wrong',
data: result.data || '',
code: result.code || '',
};
let error = new Error();
error = { ...error, ...responseError };
throw (error);
}
Here we are throwing and error that we created, since Error ctor approve only string, Im creating the plain Error js object, and the use will be:
try {
const userSaved = await apiCall(data); // calling fetch
debug.log('Success saving user', userSaved); // handle success
} catch (e) {
debug.log('Failed saving user', userSaved); // handle error
}
Solution 3: Using customer error
async _fetch(request) {
const fetchResult = await fetch(request);
const result = await fetchResult.json();
if (fetchResult.ok) {
return result;
}
throw new ClassError(result.message, result.data, result.code);
}
And:
class ClassError extends Error {
constructor(message = 'Something went wrong', data = '', code = '') {
super();
this.message = message;
this.data = data;
this.code = code;
}
}
Hope it helped.
2021 TypeScript Answer
What I do is write a fetch wrapper that takes a generic and if the response is ok it will auto .json() and type assert the result, otherwise the wrapper throws the response
export const fetcher = async <T>(input: RequestInfo, init?: RequestInit) => {
const response = await fetch(input, init);
if (!response.ok) {
throw response;
}
return response.json() as Promise<T>;
};
and then I'll catch errors and check if they are an instanceof Response. That way TypeScript knows that error has Response properties such as status statusText body headers etc. and I can apply a custom message for each 4xx 5xx status code.
try {
return await fetcher<LoginResponse>("http://localhost:8080/login", {
method: "POST",
headers: {
Accept: "application/json",
"Content-Type": "application/json",
},
body: JSON.stringify({ email: "user#example.com", password: "passw0rd" }),
});
} catch (error) {
if (error instanceof Response) {
switch (error.status) {
case 401:
throw new Error("Invalid login credentials");
/* ... */
default:
throw new Error(`Unknown server error occured: ${error.statusText}`);
}
}
throw new Error(`Something went wrong: ${error.message || error}`);
}
and if something like a network error occurs it can be caught outside of the instanceof Response check with a more generic message i.e.
throw new Error(`Something went wrong: ${error.message || error}`);
The answer by #fny (the accepted answer) didn't work for me. The throw new Error() wasn't getting picked up by the .catch. My solution was to wrap the fetch with a function that builds a new promise:
function my_fetch(url, args) {
return new Promise((resolve, reject) => {
fetch(url, args)
.then((response) => {
response.text().then((body) => {
if (response.ok) {
resolve(body)
} else {
reject(body)
}
})
})
.catch((error) => { reject(error) })
})
}
Now every error and non-ok return will be picked up by the .catch method:
my_fetch(url, args)
.then((response) => {
// Do something with the response
})
.catch((error) => {
// Do something with the error
})
function handleErrors(response) {
if (!response.ok) {
throw Error(response.statusText);
}
return response;
}
fetch("https://example.com/api/users")
.then(handleErrors)
.then(response => console.log("ok") )
.catch(error => console.log(error) );
I wasn't satisfied with any of the suggested solutions, so I played a bit with Fetch API to find a way to handle both success responses and error responses.
Plan was to get {status: XXX, message: 'a message'} format as a result in both cases.
Note: Success response can contain an empty body. In that case we fallback and use Response.status and Response.statusText to populate resulting response object.
fetch(url)
.then(handleResponse)
.then((responseJson) => {
// Do something with the response
})
.catch((error) => {
console.log(error)
});
export const handleResponse = (res) => {
if (!res.ok) {
return res
.text()
.then(result => JSON.parse(result))
.then(result => Promise.reject({ status: result.status, message: result.message }));
}
return res
.json()
.then(result => Promise.resolve(result))
.catch(() => Promise.resolve({ status: res.status, message: res.statusText }));
};
I just checked the status of the response object:
$promise.then( function successCallback(response) {
console.log(response);
if (response.status === 200) { ... }
});
Hope this helps for me throw Error is not working
function handleErrors(response) {
if (!response.ok) {
return new Promise((resolve, reject) => {
setTimeout(() => {
reject({
status: response.status,
statusText: response.statusText,
});
}, 0);
});
}
return response.json();
}
function clickHandler(event) {
const textInput = input.value;
let output;
fetch(`${URL}${encodeURI(textInput)}`)
.then(handleErrors)
.then((json) => {
output = json.contents.translated;
console.log(output);
outputDiv.innerHTML = "<p>" + output + "</p>";
})
.catch((error) => alert(error.statusText));
}
Another (shorter) version that resonates with most answers:
fetch(url)
.then(response => response.ok ? response.json() : Promise.reject(response))
.then(json => doStuff(json)) //all good
//next line is optional
.catch(response => handleError(response)) //handle error

How to handle Promise that returns a 404 status?

I have a method that uses node-fetch to make a POST call to update a profile object in a table via an API. If an invalid profileId is provided (status 404) the promise still resolves. What's the best way to handle it so that I can only accept status 200? The method is defined as:
async function updateUserProfileSocketId(profileId, socketId) {
const body = { id: profileId, socketId };
try {
const response = await fetch(`${API_URL}/updateUserProfile`, {
method: 'post',
body: JSON.stringify(body),
headers: { 'Content-Type': 'application/json' },
});
if (response.status !== 200) {
throw new Error(response.status);
}
} catch (err) {
console.log(`updateUserProfileSocketId Error: ${err}`);
}
}
And the method is called in a service class like this:
onInit(socket) {
socket.on('init', (profile) => {
Promise.resolve(updateUserProfileSocketId(profile.id, socket.id))
.then((response) => {
if (response === null || response === undefined) {
console.log(`Unable to find profile ${profile.id}`);
socket.conn.close();
} else {
users.push(profile.id);
}
})
.catch((err) => {
console.log(err);
});
});
}
This seems to work, but I'm not sure if this is the best way to handle this. Any ideas?
If the response status is not 200, you throw an exception that will immediately be caught again. This is probably not what you want. You can leave the catch block for logging purposes, but you should rethrow the exception:
async function updateUserProfileSocketId(profileId, socketId) {
const body = { id: profileId, socketId };
try {
const response = await fetch(...);
if (response.status !== 200) {
throw new Error(response.status);
}
} catch (err) {
console.log(`updateUserProfileSocketId Error: ${err}`);
throw err;
}
}
The same thing applies to the catch-handler inside the socket-callback.
However, removing the try/catch/log/rethrow logic and handling the exception centrally would be cleaner.

Cannot set headers after they are sent to the client when I try to make an update request

I am trying to create a CRUD. I have the UPDATE created, but when I try to request from postman, I get the following error:
Error [ERR_HTTP_HEADERS_SENT]: Cannot set headers after they are sent
to the client
at ServerResponse.setHeader (_http_outgoing.js:526:11)
at ServerResponse.header (E:\freebooks-core-api\node_modules\express\lib\response.js:771:10)
at ServerResponse.send (E:\freebooks-core-api\node_modules\express\lib\response.js:170:12)
at ServerResponse.json (E:\freebooks-core-api\node_modules\express\lib\response.js:267:15)
at ServerResponse.send (E:\freebooks-core-api\node_modules\express\lib\response.js:158:21)
at Object.exports.success (E:\freebooks-core-api\network\response.js:3:6)
at E:\freebooks-core-api\components\book\network.js:32:18
at processTicksAndRejections (internal/process/task_queues.js:97:5) { code:
'ERR_HTTP_HEADERS_SENT' } (node:2844)
UnhandledPromiseRejectionWarning: Error [ERR_HTTP_HEADERS_SENT]:
Cannot set headers after they are sent to the client
at ServerResponse.setHeader (_http_outgoing.js:526:11)
at ServerResponse.header (E:\freebooks-core-api\node_modules\express\lib\response.js:771:10)
at ServerResponse.send (E:\freebooks-core-api\node_modules\express\lib\response.js:170:12)
at ServerResponse.json (E:\freebooks-core-api\node_modules\express\lib\response.js:267:15)
at ServerResponse.send (E:\freebooks-core-api\node_modules\express\lib\response.js:158:21)
at Object.exports.error (E:\freebooks-core-api\network\response.js:12:6)
at E:\freebooks-core-api\components\book\network.js:34:16
at processTicksAndRejections (internal/process/task_queues.js:97:5) (node:2844)
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 terminate the node process on unhandled promise
rejection, use the CLI flag --unhandled-rejections=strict (see
https://nodejs.org/api/cli.html#cli_unhandled_rejections_mode).
(rejection id: 1) (node:2844) [DEP0018] DeprecationWarning: Unhandled
promise rejections are deprecated. In the future, promise rejections
that are not handled will terminate the Node.js process with a
non-zero exit code.
As I read in the error, this comes from the following files:
response.js in my network folder
exports.success = function (req, res, data, status) {
res.status(status || 200)
.send({
error: '',
data
})
}
exports.error = function (req, res, error, status, log) {
console.log(log)
res.status(status || 500)
.send({
error,
data: ''
})
}
and network.js in my book (component) folder:
const router = require('express').Router()
const response = require('../../network/response')
const controller = require('./controller')
router.get('/', function (req, res) {
controller.getBooks()
.then( data => {
response.success(req, res, data, 200)
})
.catch( err => {
response.error(req, res, 'Unexpected Error', 500, err)
})
})
router.post('/', function (req, res) {
const book = req.body
console.log(book)
controller.addBook(book).then( data => {
response.success(req, res, book, 201)
})
.catch(err => {
response.error(req, res, 'Internal error', 500, err)
})
})
router.patch('/:id', function(req, res) {
const { id } = req.params
console.log(id)
const book = req.body
controller.updateBook(id, book)
.then( data => {
response.success(req, res, data, 200)
}).catch( err => {
response.error(req, res, 'Internal error', 500, err)
})
res.send('Ok')
})
module.exports = router
but since I am calling the controller and the error is running on that line, this is the code:
const store = require('./store')
function getBooks () {
return new Promise((resolve, reject) => {
resolve(store.list())
})
}
function addBook (book) {
return new Promise((resolve, reject) => {
if (!book) {
reject('I reject')
return false
} else {
resolve(book)
store.add(book)
}
})
}
function updateBook (id, book) {
return new Promise( async (resolve, reject) => {
if (!id || !book) {
reject('Invalid data')
return false
} else {
const result = await store.update(id, book)
resolve(result)
}
})
}
function deleteBook (id) {
return new Promise( async (resolve, reject) => {
if (!id) {
reject('Invalid data')
return false
} else {
const result = await store.update(id)
resolve(result)
}
})
}
module.exports = {
addBook,
getBooks,
updateBook,
deleteBook
}
finally the store
const db = require('mongoose')
const Model = require('./model')
db.Promise = global.Promise
db.connect(`mongodb+srv://fewtwtwfwe:efwefwecwecw#ferwrtervsefwg/test?retryWrites=true&w=majority`, {
useNewUrlParser: true,
useUnifiedTopology: true
}).then( () => {
console.log(`Database connected`)
}).catch( err => {
console.error(err)
})
function addBook(book) {
const myBook = new Model(book)
myBook.save()
}
async function getBooks() {
const books = await Model.find()
return books
}
async function updateBook(id, book) {
const foundBook = await Model.findOne({
'_id':id
})
foundBook.book = book
const newBook = await foundBook.save()
return newBook
}
async function deleteBook(id) {
const foundBook = await Model.findOne({
'_id':id
})
}
module.exports = {
add: addBook,
list: getBooks,
update: updateBook,
delete: deleteBook,
}
I modified the data of the connection to the database, because I am sure that the error does not lie there, I have made other requests and apparently everything is fine. Any idea?
You can only send one response for each incoming request. This particular error message tells you that your code is trying to send two requests. Most often, this occurs because of improper sequencing of code with asynchronous functions. That appears to be the case in this route:
router.patch('/:id', function(req, res) {
const { id } = req.params
console.log(id)
const book = req.body
controller.updateBook(id, book)
.then( data => {
response.success(req, res, data, 200)
}).catch( err => {
response.error(req, res, 'Internal error', 500, err)
})
res.send('Ok')
})
First it executes:
controller.updateBook()
and then while that asynchronous operation is running, it then executes:
res.send('Ok');
Then, sometime later, updateBook() finishes and it calls either the .then() or the .catch() handler and you then try to send another response to the same request.
It appears that you should just remove the res.send('Ok') entirely because you want the response to be sent according to the status of updateBook() in either the .then() or the .catch() handler.
On a completely separate topic, it's a bit of an anti-pattern to wrap a promise returning function in another promise like you are doing in updateBook(). I'd suggest you change this:
function updateBook (id, book) {
return new Promise( async (resolve, reject) => {
if (!id || !book) {
reject('Invalid data')
return false
} else {
const result = await store.update(id, book)
resolve(result)
}
})
}
to this:
function updateBook (id, book) {
if (!id || !book) {
return Promise.reject('Invalid data');
} else {
return store.update(id, book);
}
}
FYI, the reason it's an anti-pattern is that it's both unnecessary code and people often make mistakes in error handling which is exactly what you did. If store.update() rejected, you did not have a try/catch around the await to catch that error so it would have been lost and your promise would have never been resolved or rejected.
You should change all the similarly structured functions to fix this.

Categories

Resources