Uncaught (in promise); Thrown error in fetch() not being caught - javascript

I've already read tons of resources to try to help me on this. This gist did not solve it for me (https://github.com/github/fetch/issues/203#issuecomment-266034180). It also seemed like this (JavaScript Promises - reject vs. throw) would be my answer but it is not. Also this (Error thrown in awaited Promise not caught in catch block) and this (errors not being thrown after promise).
I'm developing a project using a Yii2 PHP server-side solution, and Vue frontend solution. The project has several resources (lessons, media, etc) and REST API endpoints on the server-side that all are used the same. My dev work would benefit from me creating a re-usable API client class (in native JS - not anyting Vue related). I created an 'abstract' class that I 'extend' for each resource and use its functions for the CRUD operations.
I'd like to set up some middleware functions that are going to process the response from the API so that will be handled in the same fashion after every request I make so that I don't have to reproduce that processing code in the Vue apps and components that are using those API client classes.
The code is using the native JS fetch() function. I'm using .then() and .catch() in the functions as needed to process responses and control the flow.
My problem is that I have a function to process the API response, and in it I throw an error if I receive a non-200 response. I've implemented .catch() blocks in several places but I always get an error "Uncaught (in promise)" regardless of putting catch() calls everywhere.
When a user starts watching a video, I make an API call to my server to update a status on a user_media record. So, in the Vue component, I use my UserMedia helper class to create() a resource on the server and implement a then() and catch() on that. When there is an error server-side, I expect the catch() to catch that error and handle it. But, I just get the error "Uncaught (in promise)" as if I'm not trying to catch the error at all.
In the code, I am using updateWatchedStatus() in the vimeo video component, that calls the UserMediaApi.create() which calls YiiApiHelper.request() which calls YiiApiHelper.processRestResponse() where the error is thrown. I've tried implementing catch() blocks all over the place but it's never caught.
CLEARLY, I don't understand something about either fetch(), promises, or catching errors. But I can't figure it out. It seems like the only way around this is to have to write a bunch more code to try to compensate. Any help is appreciated. Even if I'm going about this all wrong and should be doing it someway else entirely.
The full code for that can be seen here:
YiiApiHelper.js https://pastebin.com/HJNWYQXg
UserMediaApi.js https://pastebin.com/9u8jkcSP
Vimeo Video Vue Component https://pastebin.com/4dJ1TtdM
For brevity, here's what's important:
Generic API Helper:
const request = function(resource, options){
return fetch(resource, options)
.then(response => Promise.all([response, response.json()]));
}
const resourceUrl = function(){
return this.autoPluralizeResource ?
this.resourceName+'s' :
this.resourceName;
}
const create = function(postData, options){
const url = new URL(this.baseUrl+'/'+this.resourceUrl());
if(!options){
options = {};
}
options = {
method: 'POST',
body: JSON.stringify(postData),
...options,
}
if(!options.headers){
options.headers = {};
}
options.headers = {
'X-CSRF-Token': document.querySelector('meta[name="csrf-token"]').getAttribute('content'),
"Content-Type": "application/json",
...options.headers
}
return this.request(url, options)
.then(this.processRestResponse);
}
const processRestResponse = function([response, body]){
if(!response.ok){
if(response.status == 422){
if(Array.isArray(body)){
let messages = [];
body.forEach(validationError => {
messages.push(validationError.message);
})
throw {
name: response.status,
message: messages.join("\n")
}
}
}
throw {
name: response.status,
message: (body.message) ?
body.message :
response.statusText
}
}
return Promise.all([response, body]);
}
export default {
baseUrl: '',
resourceName: '',
autoPluralizeResource: true,
resourceUrl: resourceUrl,
request: request,
create: create,
processRestResponse: processRestResponse,
handleErrorResponse: handleErrorResponse
};
UserMedia helper:
import YiiApiHelper from './../../yiivue/YiiApiHelper.js';
export default {
...YiiApiHelper,
baseUrl: window.location.origin+'/media/api/v1',
resourceName: 'user-media',
autoPluralizeResource: false
}
VimeoVideo.js:
let updateWatchedStatus = function(watchedStatusId) {
if(!props.userMedia){
// --- User has no record for this media, create one
return UserMediaApi.create({
media_id: props.media.id,
user_id: props.userId,
data: {
[Helper.WATCHED_STATUS_KEY]: watchedStatusId
}
}).then(([response, body]) => {
context.emit('userMediaUpdated', {userMedia: body});
return body;
}).catch(YiiApiHelper.handleErrorResponse);;
}
// --- User has a record, update the watched status in the data
let data = {
...userMedia.value.data,
[Helper.WATCHED_STATUS_KEY]: watchedStatusId
}
return UserMediaApi.update(props.media.id+','+props.userId, {
data: data
}).then(([response, body]) => {
context.emit('userMediaUpdated', {userMedia: body});
return body;
}).catch(YiiApiHelper.handleErrorResponse);;
}

Figured out and fixed this a while ago and figured I should come back in case it helps anyone.
Wrapping the request in a promise, and passing its resolve/reject into promises returned was the solution.
The code below isn't complete but it's enough to illustrate what had to be done to get this working as intended:
const request = function(resource, options){
return new Promise((resolve, reject) => {
return fetch(resource, options)
.then(response => {
if(
options &&
options.method == "DELETE" &&
response.status == 204
){
// --- Yii2 will return a 204 response on successful deletes and
// --- running response.json() on that will result in an error
// --- "SyntaxError: Unexpected end of JSON input" so we will just
// --- avoid that by returning an empty object
return Promise.all([response, JSON.stringify("{}"), resolve, reject])
}
// --- Include resolve/reject for proper error handling by response processing
return Promise.all([response, response.json(), resolve, reject])
}).then(this.processRestResponse)
});
}
const create = function(postData, options){
const url = new URL(this.baseUrl+'/'+this.resourceUrl());
if(!options){
options = {};
}
options = {
method: 'POST',
body: JSON.stringify(postData),
...options,
}
if(!options.headers){
options.headers = {};
}
options.headers = {
'X-CSRF-Token': document.querySelector('meta[name="csrf-token"]').getAttribute('content'),
"Content-Type": "application/json",
...options.headers
}
return this.request(url, options);
}
const processRestResponse = function([response, body, resolve, reject]){
// --- If the response is okay pass it all through to the function
// --- that will be handling a response
if(response.ok){
return resolve([response, body]);
}
// --- If there are validation errors prepare them in a string
// --- to throw a user friendly validation error message
if(
response.status == 422 &&
Array.isArray(body)
){
let messages = [];
body.forEach(validationError => {
messages.push(validationError.message);
})
return reject({
name: response.status,
message: messages.join("\n")
})
}
// --- If there is another error just provide the status text
// --- as a message (Yii provides this)
return reject({
name: response.status,
message: (body.message) ?
body.message :
response.statusText
})
}
export default {
baseUrl: '',
resourceUrl: resourceUrl,
request: request,
create: create,
processRestResponse: processRestResponse,
handleErrorResponse: handleErrorResponse
};

Related

nodejs app crash on openai dall-e 2 api rejected request

I'm surely dumb, but I'm not able to figure out how to handle openai api rejected requests
( for the context, dall-e 2 is an image generator )
when user tries to generate forbidden images, my nodejs app just exits
async function start(arg) {
try{
// generate image
const response = openai.createImage({
prompt: arg,
n: 1,
size: "1024x1024",
});
// on success response
response.then(res =>{
console.log("ok");
})
response.catch(err =>{
console.log(err);
});
} catch(e){
console.log(e);
}
}
it gives me something like that on the exit :
data: {
error: {
code: null,
message: 'Your request was rejected as a result of our safety system. Your prompt may contain text that is not allowed by our safety system.',
param: null,
type: 'invalid_request_error'
}
}
tried using response.catch and try catch without success, the app just exits everytime
I at least want to ignore this error in the first place
in a second hand, I would like to console.log the given message (data.error.message)
I don't know what to do to by honest, don't even understand why try catch isn't working
With the details given, my guess would be that the Promise returned by getImages is being rejected. You could debug this a bit by adding some additional logs into your .catch callback and catch statement.
How to do this really depends on what you're trying to do with this api, the code as it's currently written would log something and exit no matter what happens.
There's a couple ways to handle this
Use your .catch to handle the error. Utilizing promise chainability you can get something like this
openai.createImage({
prompt: arg,
n: 1,
size: "1024x1024",
user: msg.author.id,
})
.catch((e) => {
if (e.data.error.message.includes('safety system')) {
return 'something'
}
console.error(e)
})
If you need the response object, the asnwer might be different. Looks like the openai package is built on axios and you can pass axios options into it. See https://axios-http.com/docs/handling_errors and the Request Options section of https://npmjs.com/package/openai
EDIT
I found my solution thanks to #JacksonChristoffersen
Basically I was getting http status 400
I just added request options from axios to validate http status smaller than 500
Here's the solution:
async function start(arg) {
try{
// generate image
const response = openai.createImage({
prompt: arg,
n: 1,
size: "1024x1024",
},{
validateStatus: function (status) {
return status < 500; // Resolve only if the status code is less than 500
}
});
// on success response
response.then(res =>{
console.log("ok");
})
response.catch(err =>{
console.log(err);
});
} catch(e){
console.log(e);
}
}

Avoid handling if error has response in Vue with Axios in every request

I use the axios interceptors to handle some errors, specially the errors without response. And in some parts of my project, I use the message contained in the error.response.data for validations and showing a messaged stored in the backend. But this interceptor is not preventing me from having to check if the error has a response.
My interceptor:
axios.interceptors.response.use(
function (response) {
...
},
function (error) {
if (!error.response) {
...
return Promise.reject(new Error(error.message))
}
An example of a request that depends on having the error.response:
this.$store.dispatch('updateField', { [this.fieldKey]: this.value ? this.value : null }).catch((error) => {
this.validateField(error.response.data)
})
But I'd have to put the validateField call inside an if(eror.response) to avoid an error in the console, and spread this if all over my code?
response can be treated as optional, because it actually is:
this.validateField(error.response?.data)
Or normalized error that contains needed properties and doesn't rely on the structure of Axios error can be created in an interceptor:
function (rawError) {
const error = new Error(error.response?.data?.myError || error.message);
error.data = error.response?.data || null;
error.headers = error.response?.headers || null;
error.status = error.response?.status || 0;
return Promise.reject(error);
}

AWS Lambda to be invoked directly from a step function (without invoking the API) and also within an API context

I am struggling to understand a basic aspect of Lambda implementation.
Problem: how to use a lambda both inside and outside of an API context?
I have a lambda (nodejs) with an API gateway in front of it:
MyFunction:
Type: AWS::Serverless::Function
Properties:
CodeUri: functions/myfunction/
Handler: app.lambdaHandler
Runtime: nodejs14.x
Timeout: 4
Policies:
- DynamoDBCrudPolicy:
TableName: MyTable
Events:
ApiEvent:
Type: Api
Properties:
Path: /myfunc
Method: any
The handler is used to read (GET) or write (POST) into a a DynamoDB table and returns accordingly. If no method is passed it assumes GET.
exports.lambdaHandler = async (event) => {
const method = event.httpMethod ? event.httpMethod.toUpperCase() : "GET";
try {
switch (method) {
case "GET":
// assuming an API context event.queryStringParameters might have request params. If not the parameters will be on the event
const params = event.queryStringParameters ? event.queryStringParameters : event;
// read from dynamo db table then return an API response. what if outside an API context?
return {
statusCode: 200,
body: JSON.stringify({ message: "..." })
};
case "POST":
// similarly the body will be defined in an API context
const body = typeof event.body === "string" ? JSON.parse(event.body) : event.body;
// write to dynamo db table
return {
statusCode: 200,
body: JSON.stringify({ message: "..." })
};
default:
return {
statusCode: 400,
body: JSON.stringify({ error: "method not supported" })
};
}
} catch (error) {
// this should throw an Error outside an API context
return {
statusCode: 400,
body: JSON.stringify({ error: `${typeof error === "string" ? error : JSON.stringify(error)}` })
};
}
}
Is there an easy way to refactor this code to support both scenarios? For example a step function could call this lambda as well. I know I can have the step function invoking an API but I think this is overkill as step functions support invoking lambdas directly.
I see 2 ways I can go about this:
The lambda has to be aware of whether it is being invoked within an API context or not. It needs to check if there's a http method, queryStringParameters and build its input from these. Then it needs to return a response accordingly as well. A stringified JSON with statusCode or something else, including throwing an Error if outside an API call.
The lambda assumes it is being called from an API. Then the step function needs to format the input to simulate the API call. The problem is that the response will be a string which makes it difficult to process inside a step function. For example assigning it to a ResultPath or trying to decide if there was an error or not inside a choice.
Additionally I could have the step function calling an API directly or the last resort would be to have 2 separate lambdas where the API lambda calls the other one but this will incur additional costs.
Thoughts? Thanks.
This is where middlewares like MIDDY come into picture.
All the logic to determine event type, event source and parsing will be abstracted out and actual business logic always coded to use standard input.
we can add as many layers as we need and send standard schema as lambda input.
Typically, events may come from Api Gateway, Step function, Kinesis, SQS, etc and same lambda works for any event source.
export const handler = middy((event, context, callback) => {
const mainProcess = async () => {
const response = {}
// Busines Logic using event
return response;
};
mainProcess()
.then((result) => {
callback(null, result);
})
.catch((error) => {
callback(error);
});
})
.use({
before: (hndlr, next) => {
const parsedEvent = parseApiGatewayRequest(hndlr.event);
if (parsedEvent) {
hndlr.event = parsedEvent;
}
next();
},
})
.use({
before: (hndlr, next) => {
const parsedEvent = parseStepFuncRequest(hndlr.event);
if (parsedEvent) {
hndlr.event = parsedEvent;
}
next();
},
});

How can you use axios interceptors?

I have seen axios documentation, but all it says is
// Add a request interceptor
axios.interceptors.request.use(function (config) {
// Do something before request is sent
return config;
}, function (error) {
// Do something with request error
return Promise.reject(error);
});
// Add a response interceptor
axios.interceptors.response.use(function (response) {
// Do something with response data
return response;
}, function (error) {
// Do something with response error
return Promise.reject(error);
});
Also many tutorials only show this code but I am confused what it is used for, can someone please give me simple example to follow.
To talk in simple terms, it is more of a checkpoint for every HTTP action. Every API call that has been made, is passed through this interceptor.
So, why two interceptors?
An API call is made up of two halves, a request, and a response. Since it behaves like a checkpoint, the request and the response have separate interceptors.
Some request interceptor use cases -
Assume you want to check before making a request if your credentials are valid. So, instead of actually making an API call, you can check at the interceptor level that your credentials are valid.
Assume you need to attach a token to every request made, instead of duplicating the token addition logic at every Axios call, you can make an interceptor that attaches a token on every request that is made.
Some response interceptor use cases -
Assume you got a response, and judging by the API responses you want to deduce that the user is logged in. So, in the response interceptor, you can initialize a class that handles the user logged in state and update it accordingly on the response object you received.
Assume you have requested some API with valid API credentials, but you do not have the valid role to access the data. So, you can trigger an alert from the response interceptor saying that the user is not allowed. This way you'll be saved from the unauthorized API error handling that you would have to perform on every Axios request that you made.
Here are some code examples
The request interceptor
One can print the configuration object of axios (if need be) by doing (in this case, by checking the environment variable):
const DEBUG = process.env.NODE_ENV === "development";
axios.interceptors.request.use((config) => {
/** In dev, intercepts request and logs it into console for dev */
if (DEBUG) { console.info("✉️ ", config); }
return config;
}, (error) => {
if (DEBUG) { console.error("✉️ ", error); }
return Promise.reject(error);
});
If one wants to check what headers are being passed/add any more generic headers, it is available in the config.headers object. For example:
axios.interceptors.request.use((config) => {
config.headers.genericKey = "someGenericValue";
return config;
}, (error) => {
return Promise.reject(error);
});
In case it's a GET request, the query parameters being sent can be found in config.params object.
The response interceptor
You can even optionally parse the API response at the interceptor level and pass the parsed response down instead of the original response. It might save you the time of writing the parsing logic again and again in case the API is used in the same way in multiple places. One way to do that is by passing an extra parameter in the api-request and use the same parameter in the response interceptor to perform your action. For example:
//Assume we pass an extra parameter "parse: true"
axios.get("/city-list", { parse: true });
Once, in the response interceptor, we can use it like:
axios.interceptors.response.use((response) => {
if (response.config.parse) {
//perform the manipulation here and change the response object
}
return response;
}, (error) => {
return Promise.reject(error.message);
});
So, in this case, whenever there is a parse object in response.config, the manipulation is done, for the rest of the cases, it'll work as-is.
You can even view the arriving HTTP codes and then make the decision. For example:
axios.interceptors.response.use((response) => {
if(response.status === 401) {
alert("You are not authorized");
}
return response;
}, (error) => {
if (error.response && error.response.data) {
return Promise.reject(error.response.data);
}
return Promise.reject(error.message);
});
You can use this code for example, if you want to catch the time that takes from the moment that the request was sent until the moment you received the response:
const axios = require("axios");
(async () => {
axios.interceptors.request.use(
function (req) {
req.time = { startTime: new Date() };
return req;
},
(err) => {
return Promise.reject(err);
}
);
axios.interceptors.response.use(
function (res) {
res.config.time.endTime = new Date();
res.duration =
res.config.time.endTime - res.config.time.startTime;
return res;
},
(err) => {
return Promise.reject(err);
}
);
axios
.get("http://localhost:3000")
.then((res) => {
console.log(res.duration)
})
.catch((err) => {
console.log(err);
});
})();
It is like a middle-ware, basically it is added on any request (be it GET, POST, PUT, DELETE) or on any response (the response you get from the server).
It is often used for cases where authorisation is involved.
Have a look at this: Axios interceptors and asynchronous login
Here is another article about this, with a different example: https://medium.com/#danielalvidrez/handling-error-responses-with-grace-b6fd3c5886f0
So the gist of one of the examples is that you could use interceptor to detect if your authorisation token is expired ( if you get 403 for example ) and to redirect the page.
I will give you more practical use-case which I used in my real world projects. I usually use, request interceptor for token related staff (accessToken, refreshToken), e.g., whether token is not expired, if so, then update it with refreshToken and hold all other calls until it resolves. But what I like most is axios response interceptors where you can put your apps global error handling logic like below:
httpClient.interceptors.response.use(
(response: AxiosResponse) => {
// Any status code that lie within the range of 2xx cause this function to trigger
return response.data;
},
(err: AxiosError) => {
// Any status codes that falls outside the range of 2xx cause this function to trigger
const status = err.response?.status || 500;
// we can handle global errors here
switch (status) {
// authentication (token related issues)
case 401: {
return Promise.reject(new APIError(err.message, 409));
}
// forbidden (permission related issues)
case 403: {
return Promise.reject(new APIError(err.message, 409));
}
// bad request
case 400: {
return Promise.reject(new APIError(err.message, 400));
}
// not found
case 404: {
return Promise.reject(new APIError(err.message, 404));
}
// conflict
case 409: {
return Promise.reject(new APIError(err.message, 409));
}
// unprocessable
case 422: {
return Promise.reject(new APIError(err.message, 422));
}
// generic api error (server related) unexpected
default: {
return Promise.reject(new APIError(err.message, 500));
}
}
}
);
How about this. You create a new Axios instance and attach an interceptor to it. Then you can use that interceptor anywhere in your app
export const axiosAuth = axios.create()
//we intercept every requests
axiosAuth.interceptors.request.use(async function(config){
//anything you want to attach to the requests such as token
return config;
}, error => {
return Promise.reject(error)
})
//we intercept every response
axiosAuth.interceptors.request.use(async function(config){
return config;
}, error => {
//check for authentication or anything like that
return Promise.reject(error)
})
Then you use axiosAuth the same way you use axios
This is the way I used to do in my project. The code snippet refers how to use access and refresh token in the axios interceptors and will help to implements refresh token functionalities.
const API_URL =
process.env.NODE_ENV === 'development'
? 'http://localhost:8080/admin/api'
: '/admin-app/admin/api';
const Service = axios.create({
baseURL: API_URL,
headers: {
Accept: 'application/json',
},
});
Service.interceptors.request.use(
config => {
const accessToken = localStorage.getItem('accessToken');
if (accessToken) {
config.headers.common = { Authorization: `Bearer ${accessToken}` };
}
return config;
},
error => {
Promise.reject(error.response || error.message);
}
);
Service.interceptors.response.use(
response => {
return response;
},
error => {
let originalRequest = error.config;
let refreshToken = localStorage.getItem('refreshToken');
const username = EmailDecoder(); // decode email from jwt token subject
if (
refreshToken &&
error.response.status === 403 &&
!originalRequest._retry &&
username
) {
originalRequest._retry = true;
return axios
.post(`${API_URL}/authentication/refresh`, {
refreshToken: refreshToken,
username,
})
.then(res => {
if (res.status === 200) {
localStorage.setItem(
'accessToken',
res.data.accessToken
);
localStorage.setItem(
'refreshToken',
res.data.refreshToken
);
originalRequest.headers[
'Authorization'
] = `Bearer ${res.data.accessToken}`;
return axios(originalRequest);
}
})
.catch(() => {
localStorage.clear();
location.reload();
});
}
return Promise.reject(error.response || error.message);
}
);
export default Service;
I have implemented in the following way
httpConfig.js
import axios from 'axios'
import { baseURL } from '../utils/config'
import { SetupInterceptors } from './SetupInterceptors'
const http = axios.create({
baseURL: baseURL
})
SetupInterceptors(http)
export default http
SetupInterceptors.js
import { baseURL } from '../utils/config'
export const SetupInterceptors = http => {
http.interceptors.request.use(
config => {
config.headers['token'] = `${localStorage.getItem('token')}`
config.headers['content-type'] = 'application/json'
return config
},
error => {
return Promise.reject(error)
}
)
http.interceptors.response.use(function(response) {
return response
}, function (error) {
const status = error?.response?.status || 0
const resBaseURL = error?.response?.config?.baseURL
if (resBaseURL === baseURL && status === 401) {
if (localStorage.getItem('token')) {
localStorage.clear()
window.location.assign('/')
return Promise.reject(error)
} else {
return Promise.reject(error)
}
}
return Promise.reject(error)
})
}
export default SetupInterceptors
Reference : link

How to manage axios errors globally or from one point

I have the standard then/catch axios code all over my app, a simple one goes likes this..
axios.get('/').then( r => {} ).catch( e => {} )
The problem I have with the above is that I have to duplicate the catch() block to handle any potential errors that my be invoked in my app and my questions is, if there is anything I can do to catch the errors globally from on entry point as opposed to using catch everywhere.
I am looking for solutions from either axios side or vue, since my app is built with vue
You should use an interceptor.
First, create an axios instance using the create method. This is what you would need to use throughout your app instead of referencing axios directly. It would look something like this:
let api = axios.create({
baseURL: 'https://example.com/api/',
timeout: 1000,
headers: {'X-Custom-Header': 'foobar'}
});
Then attach an interceptor to your axios instance to be called after the response to each of the requests for that instance:
api.interceptors.response.use((response) => response, (error) => {
// whatever you want to do with the error
throw error;
});
While just handling errors globally inside the interceptor works in some case, there are times when you'd want more control as to whether the error should be handled globally.
I personally compose errors globally and call the handlers locally. With this approach, i can decide to not handle the error globally in some cases. I can also decide to invoke the global handler only when certain conditions are met.
Below is a simple implementation of a globally composed error handler.
To better understand this technique, you may want to check this article (A short story on ajax error handlers).
import axios from 'axios';
import {notifier} from './util';
// errorComposer will compose a handleGlobally function
const errorComposer = (error) => {
return () => {
const statusCode = error.response ? error.response.status : null;
if (statusCode === 404) {
notifier.error('The requested resource does not exist or has been deleted')
}
if (statusCode === 401) {
notifier.error('Please login to access this resource')
}
}
}
axios.interceptors.response.use(undefined, function (error) {
error.handleGlobally = errorComposer(error);
return Promise.reject(error);
})
// Fetch some missing information
axios.get('/api/articles/not-found').then(resp => {
// Do something with article information
}).catch(error => {
const statusCode = error.response ? error.response.status : null;
// We will handle locally
// When it's a 404 error, else handle globally
if (statusCode === 404) {
// Do some specific error handling logic for this request
// For example: show the user a paywall to upgrade their subscription in order to view achieves
} else {
error.handleGlobally && error.handleGlobally();
}
})
// Fetch some missing information
axios.get('/api/users/not-found').then(resp => {
// Do something with user information
}).catch(error => {
// We want to handle globally
error.handleGlobally && error.handleGlobally();
})
You could use Axios Multi API. It solves this issue by having a simple onError callback that you can set when you create your API (disclaimer: I'm the author of the package). I in fact created it because I was tired of reinventing the wheel in many projects.
import { createApiFetcher } from 'axios-multi-api';
const api = createApiFetcher({
apiUrl: 'https://example.com/api/',
apiEndpoints: {
getUserDetails: {
method: 'get',
url: '/user-details/get',
},
},
onError(error) {
console.log('Request has failed', error);
}
});
const data = api.getUserDetails({ userId: 1 });

Categories

Resources