catching exception thrown by service worker message event - javascript

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) => {})

Related

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.

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.

How to handle API response errors in async/await

I'm trying to better understand error handling while using async/await, third party libs, and external APIs.
Given the following pseudo-code:
const createConnectionRequest = async (args) => {
try {
const { data } = axios.post(url, args);
return data;
} catch (err) {
throw new Error(err);
}
}
My understanding is the throw would occur a result of the axios.post failing rather than an issue with my request.
If the response from my API was 200 but included an error of some sort, eg.
{
status: 200,
error: 'Invalid fields supplied',
}
Would I throw this error in the try block and expect a parent calling function to catch it?
const createConnectionRequest = async (args) => {
try {
const { data } = axios.post(url, args);
if (data.error) {
throw new Error(data.error);
}
return data;
} catch (err) {
throw new Error(err);
}
}
...
const processor = async () => {
try {
await createConnectionRequest(...);
} catch (err) {
// handle error from API response here
throw new Error(err);
}
}

How to notify the main function that a call to an asynchronous function fails?

Having a validation function, in this function there are three calls to asynchronous functions that execute independent conditions of the validation process, these asynchronous functions return the following object:
{status: true|false, message: void|string}
status is set to true if the validation in turn is successfully otherwise it is set to false and a message is added.
The requirement is: The validator function must return early, that is, if any asynchronous validation fails, it must stop there and return the reason for the failure in the object.
I attach an emulation of the implementation using callbacks because I can not use async/await:
function validator() {
validation1(response => {
if (response.status === false) {
console.log('Here in validation1 validator needs to return response');
}
});
validation2(response => {
if (response.status === false) {
console.log('Here in validation2 validator needs to return response');
}
});
validation3(response => {
if (response.status === false) {
console.log('Here in validation3 validator needs to return response');
}
});
}
function validation1(callback) {
setTimeout(() => {
const object = {
status: random(),
message: 'message from validation 1'
};
callback(object);
}, 2000);
}
function validation2(callback) {
setTimeout(() => {
const object = {
status: random(),
message: 'message from validation 2'
};
callback(object);
}, 1000);
}
function validation3(callback) {
setTimeout(() => {
const object = {
status: random(),
message: 'message from validation 3'
};
callback(object);
}, 3000);
}
// Helper function for generating random boolean
const random = () => Math.random() >= 0.5;
validator();
How can I notify the validator function that an error occurred and consequently return?
Thanks for your comments.
Regarding this question has already been answered in this question I think they do not address the same issue. One deals with synchronicity and asynchrony in javascript instead this question asks about how to handle sequential asynchronous functions and the strategy of how to handle when any of these functions fail.
Thanks to Paul's comment about error-first callback pattern I remember the article about callbacks in javascript.info. After reading I came to this solution.
function validatior() {
validation1((error, success) => {
if (error) {
console.log(error.message);
} else {
validation2((error, success) => {
if (error) {
console.log(error.message);
} else {
validation3((error, success) => {
if (error) {
console.log(error.message);
} else {
console.log(success);
}
});
}
});
}
});
}
function validation1(callback) {
setTimeout(() => {
// callback(new Error('Error message from validation 1'));
callback(null, true);
}, 1000);
}
function validation2(callback) {
setTimeout(() => {
// callback(new Error('Error message from validation 2'));
callback(null, true);
}, 2000);
}
function validation3(callback) {
setTimeout(() => {
callback(new Error('Error message from vallidation 3'));
// callback(null, true);
}, 3000);
}
validatior();
This example will stop at validation3 stopping the validator process. I know this is looking like a callback hell, but there is in the same post an example about how to alleviate the problem.
Instead of returning false you could return a promise that resolves on success and gets rejected when the validation fails, e.g.:
const timer = ms => new Promise(resolve => setTimeout(resolve, ms));
async function validate() {
await timer(1000);
if(/*isValid:*/ false) {
throw new Error("Validation failed"); // rejection
} else {
return true; // resolve
}
}
Then you can easily turn multiple promises into one:
Promise.all([
validate(),
validate(),
validate()
]).then(() => {
// all fine
}, error => {
// at least one failed
console.log(error.message);
});

Pass error after catch on promises

Hi I'm new so sorry if my question does not formulate properly.
I want to define a promise from axios js in a global function.
Here I want to handle / catch the 401 status globally and logout the user.
I do not want to handle it in every single query.
Here my source global function to handle a request:
export function requestData (url, payload = {}) {
return axios.post(url, payload)
.then(response => {
return response.data
})
.catch(error => {
if (error.response.status === 401) {
logout()
} else {
return error
}
})
}
And here a example function I use on a controller:
requestData('/api/persons', {options: this.options, search: search})
.then(data => {
this.data = data
})
.catch(error => {
this.error = error.toString()
})
My Problem is that the promise catch in my controller will not fire when there is an exception. How to realize this?
change return error in your requestData function to throw error
As per the Axios docs
You can intercept requests or responses before they are handled by then or catch.
You're going to want to use the Response Interceptor:
axios.interceptors.response.use(function(response) {
// Do something with response data
return response;
}, function(error) {
// Do something with response error
if (error.status === 401) {
logout()
}
return Promise.reject(error);
});
Replacing return error by throw error is the half work.
When I'm right the throw error in promise catch will not invoke the next promise .catch statement. This will work in the .then statement.
This way it should work:
export function requestData (url, payload = {}) {
return axios.post(url, payload)
.then(response => {
return response.data
})
.catch(error => {
if (error.response.status === 401) {
logout()
} else {
return error
}
})
.then(result => {
if (result instanceof Error) {
throw result
} else {
return result
}
})
}

Categories

Resources