How to prevent route change based on async function [duplicate] - javascript

This question already has answers here:
try/catch blocks with async/await
(10 answers)
Closed 8 months ago.
In my Next.js app, I'm calling an upload function which contains the then and catch functions.
export const uploadDocument = async (url: UploadURLs, file: File) => {
const formData = new FormData();
formData.append("file", file);
await fetch(`${someEndpoint}`, {
method: "POST",
body: formData,
})
.then(() => notify(`Awesome, it worked.`, "success"))
.catch(() => notify("An error occurred.", "error"));
};
Note: The notify function is a wrapper function firing a toast notification, and isn't really important to my question.
From my component, I'm calling the uploadDocument function. Everything is working, but I can't stop router.push(nextPage) from firing (shown below), regardless of whether the uploadDocument function succeeds or not.
I want to keep the then and catch logic inside the uploadDocument function, but also would like to have the calling component know whether or not the upload succeeded, so I could prevent the page change.
const handleSubmit = async (e: React.FormEvent) => {
e.preventDefault();
await uploadDocument(
"certificate",
file
)
router.push(nextPage); // This always fires, but should be conditional
};
I tried to capture the result of the function call, but get undefined regardless of upload success/failure:
const result = await uploadDocument("certificate", file);
if (result.success) router.push(nextPage);

Try this approach:
export const uploadDocument = (url: UploadURLs, file: File) => {
const formData = new FormData();
formData.append("file", file);
return fetch(`${someEndpoint}`, {
method: "POST",
body: formData,
})
};
Then you can catch the error with an try /catch
const handleSubmit = async (e: React.FormEvent) => {
e.preventDefault();
try {
await uploadDocument(
"certificate",
file
)
notify(`Awesome, it worked.`, "success")
router.push(nextPage);
} catch(err) {
notify("An error occurred.", "error")
}
};

I followed the advice of #kemicofa-ghost, as they pointed out in the comments, and arrived at a working solution.
export const uploadDocument = async (url: UploadURLs, file: File) => {
const formData = new FormData();
formData.append("file", file);
try {
await fetch(`${someEndpoint}`, {
method: "POST",
body: formData,
});
notify(`Your file ${file.name} was uploaded successfully`, "success");
return true;
} catch (error) {
notify(
"An error occurred while uploading the file. Please try again.",
"error"
);
return false;
}
};
And then:
const result = await uploadDocument(
"certificate",
companyInfo2.incorporationCertificate
);
if (result) router.push(nextPage);

You can keep your logic in your function:
type UploadResponse = {
error: boolean;
message: string;
}
export const uploadDocument = async (url: UploadURLs, file: File): Promise<UploadResponse> => {
try {
const formData = new FormData();
formData.append("file", file);
await fetch(`${someEndpoint}`, {
method: "POST",
body: formData,
})
return {error: false, message: "Awesome, it worked"}
} catch(e) {
return {error: true, message: "An error occurred"}
}
};
const handleSubmit = async (e: React.FormEvent) => {
e.preventDefault();
const result = await uploadDocument(
"certificate",
file
)
notify(result.message);
if (!result.error) {
router.push(nextPage);
}
};

Related

How to send text and formData in one fetch call

I am trying to send text and formData in one fetch call to a Node.js backend using multer.
I can send formData on its own with no issues, but when I try and add text, the api call stays 'pending'.
Here is my fetch call that works just with formData:
const handleImage = async (e) => {
var formData = new FormData();
let file = e.target.files[0];
formData.append("image", file);
try {
const upload = await fetch(
`${process.env.NEXT_PUBLIC_SERVER_API}/uploadImage`,
{
method: "POST",
body: formData,
}
);
} catch (e) {
console.log("Something went wrong!");
}
};
Here is the same fetch call with text added that does not work:
const handleImage = async (e) => {
var formData = new FormData();
let file = e.target.files[0];
formData.append("image", file);
try {
const upload = await fetch(
`${process.env.NEXT_PUBLIC_SERVER_API}/uploadImage`,
{
method: "POST",
body: {formData, userId}
}
);
} catch (e) {
console.log("Something went wrong!");
}
};
It also doesn't work if I try and user JSON.stringify().
I do believe that you can't send a formData and json body at the same time (maybe there is a way somehow i don't know)
because multer will just take the file from formdata and the other property will be set to req.body so if you want to send userId you can try
const handleImage = async (e) => {
var formData = new FormData();
let file = e.target.files[0];
formData.append("image", file);
formData.append("userId", userId);
try {
const upload = await fetch(
`${process.env.NEXT_PUBLIC_SERVER_API}/uploadImage`,
{
method: "POST",
body: formData,
}
);
} catch (e) {
console.log("Something went wrong!");
}
};

Response of fetching binary data in Postman is successful, but in console returns undefined

I am trying to fetch binary from API using POST request.
const generateReportData = async (params: ReportParams) => {
const response = await fetchReportService(
{ path: "GenerateReport", method: "POST", body: params }).catch();
console.log(response, 'RESP'); // returns undefined for some reason;
const blob = new Blob([response], { type: "application/octet-stream" });
const fileName = "report.doc";
saveAs(blob, fileName);
};
export const fetchReportService = ({ path, method, query, body, options = {} }: FetchParams) => {
return fetch(Config.api.report, path, {
headers,
method,
body: body ? JSON.stringify(body) : undefined,
...options,
}, query);
};
In this method is response undefined for some reason. However if I check my network tab, the request has 200 code and the response looks like this:
I need this response to be accessible from my code. Any ideas why is the response in my method undefined?
Thank you in advance.
Update:
It is weird that you got undefined. From the code you post you should get at least something non-null.
I'd advice you to do the following to debug:
remove the tailing empty .catch()
const response = fetchReportService(...) without await keyword, then console.log it to make ensure it's an instance of Response builtin class.
If it's still undefined, then you should verify that fetch === window.fetch inside fetchReportService. You might mistakenly import fetch from 'somewhere' and using a wrong fetch function.
Might as well verify that window.fetch.toString() == "function fetch() { [native code] }", to ensure your runtime is not tampered.
Original answer:
This line is problematic.
const blob = new Blob([response], { type: "application/octet-stream" });
If you don't care about the MIME type and simply wanna obtain the binary blob from the response, you can do:
const blob = await response.blob();
Or, you can construct the blob manually from arrayBuffer.
const blob = new Blob([await response.arrayBuffer()], { type: "application/octet-stream" });
You need remove '.catch()'
const generateReportData = async (params: ReportParams) => {
try {
const response = await fetchReportService(
{ path: "GenerateReport", method: "POST", body: params });
console.log(response, 'RESP'); // returns undefined for some reason;
const blob = new Blob([response], { type: "application/octet-stream" });
const fileName = "report.doc";
saveAs(blob, fileName);
} catch (e) {
console.log(e)
}
};
export const fetchReportService = ({ path, method, query, body, options = {} }: FetchParams) => {
return fetch(Config.api.report, path, {
headers,
method,
body: body ? JSON.stringify(body) : undefined,
...options,
}, query);
};
The Fetch API call is asynchronous.
Example with async/await:
export const fetchReportService = async ({ path, method, query, body, options = {} }: FetchParams) => {
try {
const res = await fetch(Config.api.report, path, {
headers,
method,
body: body ? JSON.stringify(body) : undefined,
...options,
}, query);
} catch (error) {
console.error(error);
}
return res;
};

Handling query in React and Express

Somewhere in my React application I used REST API to send request to the server. In my URL I want to use query (in the postIconsTransition method), but when I send a request to the server, server tells me could not found this URL (I build this error in my server). If I use this URL without any query the request in the postIconsTransition method works fine. postId and authContext.userId work fine, can anyone tell me what's wrong with my code?
In my component where I send request:
const likeHandler = async () => {
setLike(prevState => !prevState);
if (!like) {
try {
await postIconsTransition(props.postId, "inc");
} catch (error) {}
} else {
try {
await postIconsTransition(props.postId, "dec");
} catch (error) {}
}
};
In useHttp.js component:
const postIconsTransition = async (postId, addtionAddress) => {
return await transitionData(
`http://localhost:5000/post/${postId}/${authContext.userId}?t=${addtionAddress}`,
"POST",
null,
{ Authorization: `Bearer ${authContext.token}` }
);
};
transitionData method:
const transitionData = useCallback(
async (url, method = "GET", body = null, headers = {}) => {
setIsLoading(true);
const abortController = new AbortController();
activeHttpRequest.current.push(abortController);
try {
const response = await fetch(url, {
method,
body,
headers,
signal: abortController.signal
});
const responseData = await response.json();
activeHttpRequest.current = activeHttpRequest.current.filter(
reqCtrl => reqCtrl !== abortController
);
if (!response.ok) {
throw new Error(responseData.message);
}
setIsLoading(false);
return responseData;
} catch (error) {
modalContext.err(error);
setIsLoading(false);
throw error;
}
},
[modalContext.err]
);
In Express:
router.post(
"/:postId/:userId?t=inc",
tokenChecker,
postController.updateLikesComments
);
router.post(
"/:postId/:userId?t=dec",
tokenChecker,
postController.updateLikesComments
);
All of them work fine but when I use query in my URL, it's not working any more.
You don't specify query parameters in express routes like that. Just send them. Express can read it.
router.post(
"/:postId/:userId",
tokenChecker,
postController.updateLikesComments
);
// Notice that you don't need the other one.
and in your controller check the parameter
// controller's code
const t = req.query.t;
if (t === 'inc') {
// do what you want here
}
if (t === 'dec') {
// do what you want here
}

Service call is not going in react-native. Getting warning like "Possible unhandled Promise Rejection, Reference error: response is not defined"

I am new to react native and making service call for the first time. My problem is service call is not going and getting warning like
Possible unhandled Promise Rejection, Reference error: response is not defined.
I am trying to hit loginUser function.
Api.js
const BASE_URL = "http://localhost:8200";
export const api = async (url, method, body = null, headers = {}) => {
try {
const endPoint = BASE_URL.concat(url);
const reqBody = body ? JSON.stringify(body) : null;
const fetchParams = {method, headers};
if((method === "POST" || method === "PUT") && !reqBody) {
throw new Error("Request body required");
}
if(reqBody) {
console.log("ReQBody--->"+reqBody);
fetchParams.headers["Content-type"] = "application/json";
fetchParams.body = reqBody;
}
const fetchPromise = await fetch(endPoint, fetchParams);
const timeOutPromise = new Promise((resolve, reject) => {
setTimeout(() => {
reject("Request Timeout");
}, 3000);
});
const response = await Promise.race([fetchPromise, timeOutPromise]);
return response;
} catch (e) {
return e;
}
}
export const fetchApi = async (url, method, body, statusCode, token = null, loader = false) => {
console.log("In FetchAPi Function");
try {
const headers = {}
const result = {
token: null,
success: false,
responseBody: null
};
if(token) {
headers["securityKey"] = token;
}
const response = await api(url, method, body, headers);
console.log("fetchApi-->>"+response);
if(response.status === statusCode) {
result.success = true;
let responseBody;
const responseText = await response.text();
try {
responseBody = JSON.parse(responseText);
} catch (e) {
responseBody = responseText;
}
result.responseBody = responseBody;
return result;
}
let errorBody;
const errorText = await response.text();
try {
errorBody = JSON.parse(errorText);
} catch (e) {
errorBody = errorText;
}
result.responseBody = errorBody;
console.log("FetchApi(Result)--->>"+result);
throw result;
} catch (error) {
return error;
}
}
auth.actions.js
export const loginUser = (payload) => {
console.log("In LoginUser function2");
return async (dispatch) => {
<-----**I am not able to enter into this block**------>
try {
dispatch({
type: "LOGIN_USER_LOADING"
});
console.log("In LoginUser function3");
const response = await fetchApi("/login", "POST", payload, 200);
if(response.success) {
dispatch({
type: "LOGIN_USER_SUCCESS",
});
dispatch({
type: "AUTH_USER_SUCCESS",
token: response.token
});
dispatch({
type: "GET_USER_SUCCESS",
payload: response.responseBody
});
return response;
} else {
throw response;
}
} catch (error) {
dispatch({
type: "LOGIN_USER_FAIL",
payload: error.responseBody
});
return error;
}
}
}
In console log, I can't see anything in network tab. In the android emulator, the mentioned warning has come.
My console tab
I see that your BASE_URL is served using an http endpoint. You can only make requests to https endpoints from react native projects. A possible workaround is to use ngrok. Just download it and run ./ngrok http 8200 since your port number is 8200. It will expose an HTTPS endpoint and replace your BASE_URL with that link and try fetching the data again.
I use the following code to make API calls. See if you can integrate it in your code. it is quite simple:
In a class called FetchService:
class FetchService {
adminAuth(cb, data) {
console.log('here in the fetch service');
return fetch(
baseURL + "login",
{
method: "POST",
headers: {
Accept: "application/json",
},
body: data
}
)
.then((response) => response.json())
.then(responsej => {
cb(null, responsej);
})
.catch(error => {
cb(error, null);
});
}
}
export default FetchService;
Then call it from your component using:
import FetchService from './FetchService';
const fetcher = new FetchService;
export default class LoginScreen extends React.Component {
fetchData() {
const data = new FormData();
data.append('username',this.state.username);
data.append('password',this.state.password);
fetcher.wastereport((err, responsej) => {
if(err) {
//handle error here
} else {
//handle response here
}
}, data);
}
}

Amazon S3 Remote File Upload with Axios

I am trying to write a function that would:
Take a remote URL as a parameter,
Get the file using axios
Upload the stream to amazon s3
And finally, return the uploaded url
I found help here on stackoverflow. So far, I have this:
/*
* Method to pipe the stream
*/
const uploadFromStream = (file_name, content_type) => {
const pass = new stream.PassThrough();
const obj_key = generateObjKey(file_name);
const params = { Bucket: config.bucket, ACL: config.acl, Key: obj_key, ContentType: content_type, Body: pass };
s3.upload(params, function(err, data) {
if(!err){
return data.Location;
} else {
console.log(err, data);
}
});
return pass;
}
/*
* Method to upload remote file to s3
*/
const uploadRemoteFileToS3 = async (remoteAddr) => {
axios({
method: 'get',
url: remoteAddr,
responseType: 'stream'
}).then( (response) => {
if(response.status===200){
const file_name = remoteAddr.substring(remoteAddr.lastIndexOf('/')+1);
const content_type = response.headers['content-type'];
response.data.pipe(uploadFromStream(file_name, content_type));
}
});
}
But uploadRemoteFileToS3 does not return anything (because it's a asynchronous function). How can I get the uploaded url?
UPDATE
I have further improved upon the code and wrote a class. Here is what I have now:
const config = require('../config.json');
const stream = require('stream');
const axios = require('axios');
const AWS = require('aws-sdk');
class S3RemoteUploader {
constructor(remoteAddr){
this.remoteAddr = remoteAddr;
this.stream = stream;
this.axios = axios;
this.config = config;
this.AWS = AWS;
this.AWS.config.update({
accessKeyId: this.config.api_key,
secretAccessKey: this.config.api_secret
});
this.spacesEndpoint = new this.AWS.Endpoint(this.config.endpoint);
this.s3 = new this.AWS.S3({endpoint: this.spacesEndpoint});
this.file_name = this.remoteAddr.substring(this.remoteAddr.lastIndexOf('/')+1);
this.obj_key = this.config.subfolder+'/'+this.file_name;
this.content_type = 'application/octet-stream';
this.uploadStream();
}
uploadStream(){
const pass = new this.stream.PassThrough();
this.promise = this.s3.upload({
Bucket: this.config.bucket,
Key: this.obj_key,
ACL: this.config.acl,
Body: pass,
ContentType: this.content_type
}).promise();
return pass;
}
initiateAxiosCall() {
axios({
method: 'get',
url: this.remoteAddr,
responseType: 'stream'
}).then( (response) => {
if(response.status===200){
this.content_type = response.headers['content-type'];
response.data.pipe(this.uploadStream());
}
});
}
dispatch() {
this.initiateAxiosCall();
}
async finish(){
//console.log(this.promise); /* return Promise { Pending } */
return this.promise.then( (r) => {
console.log(r.Location);
return r.Location;
}).catch( (e)=>{
console.log(e);
});
}
run() {
this.dispatch();
this.finish();
}
}
But still have no clue how to catch the result when the promise is resolved. So far, I tried these:
testUpload = new S3RemoteUploader('https://avatars2.githubusercontent.com/u/41177');
testUpload.run();
//console.log(testUpload.promise); /* Returns Promise { Pending } */
testUpload.promise.then(r => console.log); // does nothing
But none of the above works. I have a feeling I am missing something very subtle. Any clue, anyone?
After an upload you can call the getsignedurl function in s3 sdk to get the url where you can also specify the expiry of the url as well. You need to pass the key for that function. Now travelling will update with example later.
To generate a simple pre-signed URL that allows any user to view the
contents of a private object in a bucket you own, you can use the
following call to getSignedUrl():
var s3 = new AWS.S3();
var params = {Bucket: 'myBucket', Key: 'myKey'};
s3.getSignedUrl('getObject', params, function (err, url) {
console.log("The URL is", url);
});
Official documentation link
http://docs.amazonaws.cn/en_us/AWSJavaScriptSDK/guide/node-examples.html
Code must be something like this
function uploadFileToS3AndGenerateUrl(cb) {
const pass = new stream.PassThrough();//I have generated streams from file. Using this since this is what you have used. Must be a valid one.
var params = {
Bucket: "your-bucket", // required
Key: key , // required
Body: pass,
ContentType: 'your content type',
};
s3.upload(params, function(s3Err, data) {
if (s3Err) {
cb(s3Err)
}
console.log(`File uploaded successfully at ${data.Location}`)
const params = {
Bucket: 'your-bucket',
Key: data.key,
Expires: 180
};
s3.getSignedUrl('getObject', params, (urlErr, urlData) => {
if (urlErr) {
console.log('There was an error getting your files: ' + urlErr);
cb(urlErr);
} else {
console.log(`url: ${urlData}`);
cb(null, urlData);
}
})
})
}
Please check i have update your code might its help you.
/*
* Method to upload remote file to s3
*/
const uploadRemoteFileToS3 = async (remoteAddr) => {
const response = await axios({
method: 'get',
url: remoteAddr,
responseType: 'stream'
})
if(response.status===200){
const file_name = remoteAddr.substring(remoteAddr.lastIndexOf('/')+1);
const content_type = response.headers['content-type'];
response.data.pipe(uploadFromStream(file_name, content_type));
}
return new Promise((resolve, reject) => {
response.data.on('end', (response) => {
console.log(response)
resolve(response)
})
response.data.on('error', () => {
console.log(response);
reject(response)
})
})
};
*
* Method to pipe the stream
*/
const uploadFromStream = (file_name, content_type) => {
return new Promise((resolve, reject) => {
const pass = new stream.PassThrough();
const obj_key = generateObjKey(file_name);
const params = { Bucket: config.bucket, ACL: config.acl, Key: obj_key, ContentType: content_type, Body: pass };
s3.upload(params, function(err, data) {
if(!err){
console.log(data)
return resolve(data.Location);
} else {
console.log(err)
return reject(err);
}
});
});
}
//call uploadRemoteFileToS3
uploadRemoteFileToS3(remoteAddr)
.then((finalResponse) => {
console.log(finalResponse)
})
.catch((err) => {
console.log(err);
});

Categories

Resources