How to break promise chain on error - javascript

Consider this snippet
fetch(`http://${api.host}:${api.port}/user`)
.then(function(data) {
return data.json();
}, function(err) {
throw new Error(`Couldn\'t fetch user data from server: ${err.message}`);
}).then(function(eparkUser) {
for (var key in eparkUser) {
if (eparkUser.hasOwnProperty(key) && !user.hasOwnProperty(key)) {
user[key] = eparkUser[key];
}
}
done(null, user);
}, function(err) {
throw new Error(`Couldn't parse returned json: ${err.message}`);
}).catch(function(e) {
done(e);
});
Isn't throw supposed to break the chain and trigger .catch ? How to achieve this behaviour? Becauce now both throw are getting executed and I see message:
Error: Couldn't parse returned json: Couldn't fetch user data from server: request to http://localhost:3010/user failed and that not what I want.
P.S. fetch is npm node-fetch module

No, throw does not jump to catch. It does reject the promise, and all error handlers installed on it will be invoked. In your case, that's the error handler installed by the then call. Notice that .catch(handler) is just sugar for .then(null, handler).
Your current code works like
try {
try {
try {
var data = fetch(`http://${api.host}:${api.port}/user`)
} catch(err) {
throw new Error(`Couldn\'t fetch user data from server: ${err.message}`);
}
var eparkUser = data.json();
} catch(err) {
throw new Error(`Couldn't parse returned json: ${err.message}`);
}
for (var key in eparkUser) {
if (eparkUser.hasOwnProperty(key) && !user.hasOwnProperty(key)) {
user[key] = eparkUser[key];
}
}
done(null, user);
} catch(e) {
done(e);
}
To solve your problem, you'll need to nest your handlers, and install the JSON-parse-handler only on that particular promise:
fetch(`http://${api.host}:${api.port}/user`)
.then(function (data) {
return data.json()
.then(function (eparkUser) {
for (var key in eparkUser) {
if (eparkUser.hasOwnProperty(key) && !user.hasOwnProperty(key)) {
user[key] = eparkUser[key];
}
}
return user;
}, function(err) {
throw new Error(`Couldn't parse returned json: ${err.message}`);
});
}, function(err) {
throw new Error(`Couldn\'t fetch user data from server: ${err.message}`);
})
.then(done.bind(null, null), done);

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

How to handle errors with Async and Await [Node.js]

I have already a function written with bluebird promises and I would like to rewrite it with async and await. When I have made the changes I have found out that earlier with promises the reject statement always transfers the control to called function catch block though if the catch block is already there in the file from where we are rejecting. How to handle this situation properly with async and await?. (Added comments to the code to explain the issue)
With Promise:
const callingFunc = (req, res) => {
return new Promise((resolve, reject) => {
// execute request which returns promise
functionCall()
.then((response) => {
let error;
try {
xml2js(response.body, { explicitArray: false }, (err, result) => {
if (err) {
return reject(err); /* throws the correct error to catch block of the file from where callingFunc is called*/
}
if (!_.isEmpty(result.Response.errorCode)) {
return reject(result.Response); /* throws the correct error to the catch block of the file from where callingFunc is called*/
}
return resolve(result);
});
} catch (e) {
error = new Error('xml2js conversion error');
reject(error);
}
})
.catch((error) => {
const Error = new Error('Internal Server Error');
reject(Error);
});
});
};
With async and await:
const callingFunc = (req, res) => {
try {
const response = await functionCall();
let error;
try {
xml2js(response.body, { explicitArray: false }, (err, result) => {
if (err) {
throw (err); /* throws the error to the below catch block and returning xml2js conversion error and changing behaviour*/
}
if (!_.isEmpty(result.Response.errorCode)) {
throw result.Response; /* throws the error to the below catch block and returning xml2js conversion error and changing behaviour*/
}
return result;
});
} catch (e) {
error = new Error('xml2js conversion error');
throw error;
}
} catch(error) {
const Error = new Error('Internal Server Error');
throw Error;
}
};
If functionCall returns a promise, then this code is inappropriate...
return new Promise((resolve, reject) => {
// execute request which returns promise
functionCall()
.then((response) => {
If xml2js is async using callbacks, then it is appropriate to wrap it in a promise...
// return a promise that resolves with the result of xml2js
async function xml2js_promise(body) {
return new Promise((resolve, reject) => {
xml2js(body, { explicitArray: false }, (err, result) => {
if (err) reject(err);
else if (!_.isEmpty(result.Response.errorCode)) reject(result.Response);
else resolve(result);
});
});
}
Now we can await these. There's no need to nest the try's. (And you only need the try if you're going to do something on the catch).
async callingFunction = (req, res) => {
try {
const response = await functionCall();
} catch (error) {
// do something with this error
}
try {
const result = await xml2js_promise(response.body)
} catch(error) {
// do something with this error
}
return result;
}

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);
}
}

Axios interceptors and asynchronous login

I'm implementing token authentication. My access token expires every N minutes and then a refresh token is used to log in and get a new access token.
I use Axios for my API calls. I have an interceptor set up to intercept 401 responses:
axios.interceptors.response.use(undefined, function (err) {
if (err.status === 401 && err.config && !err.config.__isRetryRequest) {
serviceRefreshLogin(
getRefreshToken(),
success => { setTokens(success.access_token, success.refresh_token) },
error => { console.log('Refresh login error: ', error) }
)
err.config.__isRetryRequest = true
err.config.headers.Authorization = 'Bearer ' + getAccessToken()
return axios(err.config);
}
throw err
})
Basically, as I intercept a 401 response, I want to do a login and then retry the original rejected request with the new tokens. My serviceRefreshLogin function calls setAccessToken() in its then block. But the problem is that
the then block happens later than the getAccessToken() in the interceptor, so the retry happens with the old expired credentials.
getAccessToken() and getRefreshToken() simply return the existing tokens stored in the browser (they manage localStorage, cookies, etc).
How would I go about ensuring statements do not execute until a promise returns?
(Here's a corresponding issue on Github: https://github.com/mzabriskie/axios/issues/266)
Just use another promise :D
axios.interceptors.response.use(undefined, function (err) {
return new Promise(function (resolve, reject) {
if (err.status === 401 && err.config && !err.config.__isRetryRequest) {
serviceRefreshLogin(
getRefreshToken(),
success => {
setTokens(success.access_token, success.refresh_token)
err.config.__isRetryRequest = true
err.config.headers.Authorization = 'Bearer ' + getAccessToken();
axios(err.config).then(resolve, reject);
},
error => {
console.log('Refresh login error: ', error);
reject(error);
}
);
}
throw err;
});
});
If your enviroment doesn't suport promises use polyfill, for example https://github.com/stefanpenner/es6-promise
But, it may be better to rewrite getRefreshToken to return promise and then make code simpler
axios.interceptors.response.use(undefined, function (err) {
if (err.status === 401 && err.config && !err.config.__isRetryRequest) {
return getRefreshToken()
.then(function (success) {
setTokens(success.access_token, success.refresh_token) ;
err.config.__isRetryRequest = true;
err.config.headers.Authorization = 'Bearer ' + getAccessToken();
return axios(err.config);
})
.catch(function (error) {
console.log('Refresh login error: ', error);
throw error;
});
}
throw err;
});
Demo https://plnkr.co/edit/0ZLpc8jgKI18w4c0f905?p=preview
Could do it in the request instead of the response, and it'd probably be cleaner since it'd avoid hitting the server when the access token's expired. Copying from this article:
function issueToken() {
return new Promise((resolve, reject) => {
return client({
...
}).then((response) => {
resolve(response);
}).catch((err) => {
reject(err);
});
});
}
client.interceptors.request.use((config) => {
let originalRequest = config;
if (tokenIsExpired && path_is_not_login) {
return issueToken().then((token) => {
originalRequest['Authorization'] = 'Bearer ' + token;
return Promise.resolve(originalRequest);
});
}
return config;
}, (err) => {
return Promise.reject(err);
});

Categories

Resources