Is it possible to make a pseudo-global promise rejection handler? - reactjs

fetch() is a great improvement over the classic XMLhttpRequest() However I was wondering if I add window.addEventListener('unhandledrejection', event => ยทยทยท); to my index.android.js and index.ios.js could I theoretically remove all of my .catch() from my promise chains and make a pseudo global promise rejection handler? I am building a react native app that relies heavily on api calls, many of which are authenticated with user tokens. When a user logs in a token is stored in asynchronous storage, but each token will expire after a certain amount of time. if a user opens the app and unwittingly attempts to make a call that requires authentication with an expired token I would like the user to be routed to the login page regardless of what call was made, or what component the call is in. I currently have each individual promise chain handling unauthenticated calls on their own. Is there a better way to do it?

You could make a custom abstraction on top of fetch, which will provide a way of communicating with API and also have some business logic, which for you case is to request an auth token or make a redirect. Then you use this abstraction in the rest of the code to make API calls. Here is a small example to demonstrate the idea:
export default function request(url, options = {}) {
const req = fetch(url)
.then((response) => {
// Global response handler
// ...
// Hand response down the promise chain
return response;
})
.catch((err) => {
// Global error handler
// Check error status and (make redirect/request auth token)
// Throw error down the promise chain if needed
throw err;
});
// Returns a promise
return req;
}
Then you use it in your code like fetch(), but instead you'll be able to modify and extend it for you needs any time.
request('http://example.com/api/post/1')
.then((response) => {
// "response" has whatever you returned from global handler
})
.catch((err) => {
// "err" is whatever you've thrown from global handler
});

Related

How to extend the ion-loading till page renders in ionic-5

I've to make 3 back-end api-call before showing the UI screen. So, I used the ion-loading below snippet,
I'm presenting the loader in 1st backend call and it get closed in the first call itself. But I've to close the loader in third api call to show the screen.
How to extend the loader till the last api call,
this.presentLoading();
async presentLoading() {
this.loading = await this.loadingController.create({
spinner: null,
cssClass: 'custom-class custom-loading',
});
await this.loading.present();
}
One way to accomplish this would be to call .then() on loadingController.create() and then process all your api calls inside there. Here's how I would organize it so that your loading controller is dismissed properly either when the 3 api calls succeed or when one of them fails. This setup assumes your api call functions return promises.
this.loadingController.create({
message: 'Loading, please wait...'
}).then((loading) => {
loading.present();
Promise.all([apiCallOne(), apiCallTwo(), apiCallThree()])
.catch((err) => {
console.log(err);
loading.dismiss();
})
.then((results) => {
// do something with results ...
loading.dismiss();
});
});

Fetch GET request hitting .catch() on anything but 200 response

I have a redux store set up with actions to handle loading accounts. The action calls a service like so:
const requestOptions = {
method: 'GET',
headers: authHeader()
};
return fetch(`http://localapi.co.uk/api/account/load/${account_id}`, requestOptions)
.then(handleResponse)
.then(account => {
if(account.account.id) {
localStorage.setItem('account', JSON.stringify(account))
}
return account;
})
.catch(redirectToLogin)
Handle response is simply a function that checks the .status and .ok properties of the response and either displays an error or logs out if the response status is 401. This works perfectly fine for POST requests. When I hit my login route, any response hits the first .then(handleResponse) and deals with it.
When I send a GET request instead like above 404s, 401s, 500s.. etc all skip the .then(handleResponse) and instead jump to my catch. The problem that causes is that because catch doesn't actually give me a response object to work with I can't check the status - I want to do different things depending on whether the get was a 401 (I want to logout) or a 500 (I want to display a user error stating what went wrong) for example.
Is there a solution that will allow me to get a response or stop my GET requests hitting the .catch and instead hit the response handler I've written?
I'm using:
a Laravel 5.7.20 back-end
a React 16.7.0 front-end
running local node server with npm start
How about having a clean async/await function. Waiting till you JSON data becomes ready and then having the rest of your if logic or returning the account object from the function and moving the rest of the logic to the caller function. Something like this:
async myFunc({ account_id}) {
const url = `http://localapi.co.uk/api/account/load/${account_id}`;
const response = await fetch(url, { headers: headers: authHeader() });
const account = await response.json();
// return account (recommended);
// Place your if logic here (not recommended because its not clean)
}

Setting Variable to Axios Call

In React, I am trying to set a variable to an axios call after I return the data.
But, when console logging the variable afterwards, it's a Promise.
How do I get the value from the promise and set it to the variable?
let fourPackProducts = axios.get('URL').then(response => {
return response.data })
console.log(fourPackProducts)
Console log screenshot
You did exactly what you are asking to do. If you want to console.log the correct data, chain another then like so,
let promiseResponse = axios.get('URL')
.then(response => response.data)
.then( data => { console.log( data ); return data });
Remember, ajax requests are async, so code after an ajax request not using async handlers will run, the compiler does not stop and wait for the request to complete
You're console logging a promise because fourPackProducts is a promise. Another part of the issue is that since the promise is asynchronous, the console log is being called before the promise response comes through. To properly set the promise response to a variable, try the following:
let promiseResponse;
let fourPackProducts = axios.get('URL').then(response => {
promiseResponse = response.data;
console.log(promiseResponse);
return promiseResponse;
})
Presumably you want to do something with this variable afterward, but if the promise is not yet completed then the variable will be undefined. In order to use the variable after it has been set to the promise response, you will need to chain promises.

Angularjs route: prevent user to manually get json data

In my angularjs app I have route /#/tasks, which fetch tasks from /tasks url. If user manually goes to /tasks url, he gets json data. Should I prevent show json data to user? What is the best practice? For example, in backend I can check if request is ajax or not. If not - redirect to base url. Backend - laravel 5.
From the title of the post having the keywords 'prevent user', it seems that you need some sort of logic to define access privileges for a particular user.
You could stick to resolving the authorization logic before a user ever accesses a route.
For this, you can take advantage of the $routeProvider's resolve property of the .when() method.
According to the docs, the resolve property is ...
An optional map of dependencies which should be injected into the controller. If any of these dependencies are promises, the router will wait for them all to be resolved or one to be rejected before the controller is instantiated.
If all the promises are resolved successfully, the values of the resolved promises are injected and $routeChangeSuccess event is fired. If any of the promises are rejected the $routeChangeError event is fired.
The resolve takes in functions (as injectable dependencies) each of which may return a promise, and if any of the promises get rejected, as quoted, an error is thrown and literally, neither the route nor the view is loaded.
For the authorization logic to work, you'll need to perform some API call to some backend function/service which either resolves that the user is permitted for the route, or rejects his/her request.
.when('/tasks', {
resolve: {
authorize: function(authService){
//some api call to get either acceptance (e.g. status code 200) or rejection (status code 500) for the user being authorized
return authService.authorize();
}
},
controller: function(){
//route controller logic
}
})
Alternatively, if you'd also want to perform something on failure of the promise, you could do so by simply chaining the promise with a catch() handler and throw an error to forcefully reject the promise that is returned from the catch() itself.
.when('/tasks', {
resolve: {
authorize: function($location, authService){
//some api call to get either acceptance or rejection for the user being
return authService.authorize()
.then(function(){
//some logic on success of the promise
})
.catch(function(){
$location.path('/auth/login/');
throw 'Authorization error';
});
}
},
controller: function(){
//route controller logic
}
})

Capture server JSON response in end-to-end test

I'm writing an end-to-end test that simulates user authentication with Protractor. A user feels in her credentials and clicks a Submit button. As a result, the server returns an access token in a JSON response that can be used for other REST API calls. I'd like to save this token to a file.
There's a similar question on capturing a response of a GET request here, but I'm not sure it's a good idea to send another request after I click the button.
How can I capture the response after a button click?
Here is my idea about how to catch HTTP responses. Protractor provides a method browser.addMockModule() (docs) - it is used to add custom Angular modules to a page, which are usually used to mock outcoming requests and provide custom response. But we do not need to mock requests, it would be enough to just listen for whatever comes from a server. It can be achieved with the help of Angular HTTP interceptors. Interceptors are used to catch a request or a response and modify it for whatever needs before in gets to it's endpoint. We can use them to collect information about what is coming from the server, store it, and then let response go forward without changes. Since this custom module and spec tests will run on the same page, information about responses can be stored in some global property. Then, when button is clicked, it would be possible to inject custom script to a page to retrieve required responses via browser.executeScript() (docs). Here is the source:
it('should intercept requests', function () {
// Inject custom Angular module on a page
// Script should be injected before you "browser.get()" the page
browser.addMockModule('e2eHttp', function () {
// Note: This function scope is not a Protractor environment
angular
.module('e2eHttp', [])
.config(function ($httpProvider) {
// add custom interceptor to all requests
$httpProvider.interceptors.push('e2eHttpInterceptor');
})
.factory('e2eHttpInterceptor', function () {
return {
response: function (response) {
// name of the global property to store responses
var global = 'e2eHttpResponses';
// responses will be grouped by url
// but you can use data from "response.config" to adapt it
// it has a lot of info about response headers, method, etc
var url = response.config.url;
window[global] = window[global] || {};
window[global][url] = window[global][url] || [];
window[global][url].push(response); // store response
// proceed without changing response
return response;
}
};
});
});
// Load the page
browser.get('#/auth/login');
$('#submit').click();
// If we are sure that response has come, then extract it
browser.executeScript(function () {
// Note: This function scope is not a Protractor environment
var global = 'e2eHttpResponses';
var uri = 'api/auth/login.json';
// extract array of stored responses for required uri
var responses = (window[global] && window[global][uri]) || [];
// return responses to spec
return responses;
}).then(function (responses) {
// and finally, we are now able to get all information we need
// about response, and in your case, save it to a file
console.log(responses);
var data = responses[0].data; // first response body as a string
});
// remove injected module not to break another specs
browser.removeMockModule('e2eHttp');
});
You can move setup and injection calls to some utility modules, so test specs would be clean.

Resources