I'm attempting to pass a file from an file input field's file list through FileReader and get the Base64 string to pass to the server via axios. The FileReader's onload function seems to get the string, but it evidently loses the string before it is returned. So, the value is undefined when I try to append it to the form data.
I'm told this is an asynchronous function, but I'm not sure where to await the promise. Can someone tell me what I'm doing wrong here?
const handleSubmit = async (e) => {
e.preventDefault()
const formData = new FormData()
// FILE READER -- DOES NOT WORK
const getImageFile = () => {
return new Promise(resolve => {
const reader = new FileReader()
reader.onload = function () {
const imageFile = reader.result
// console.log(`IMAGE FILE:\n ${imageFile}`) // imageFile IS NOT UNDEFINED HERE, BASE64 STRING
}
reader.readAsDataURL(document.getElementById("image").files[0])
})
}
////////////////////////////////
const imageFile = await getImageFile()
Array.from(document.getElementById("form").elements).forEach(element => {
switch (element.name){
case "image":
formData.append(`${element.name}`, imageFile) // UNDEFINED. WHY?
break
case "submit":
break
default:
formData.append(`${element.name}`, element.value)
}
})
console.log([...formData])
try {
const response = axios.post('http://localhost:4000/uploadShow', formData)
console.log(response)
} catch (e) {
console.log(e)
}
}
You have to create the promise yourself
const getImageFile = () => {
return new Promise(resolve => {
const reader = new FileReader()
reader.onload = function () {
resolve(reader.result)
}
reader.readAsDataURL(document.getElementById("image").files[0])
})
}
const handleSubmit = async (e) => {
// ...
const imageFile = await getImageFile()
// ...
Related
I want to create an image uplaoder based on base64 and I want to get results as an array but I got empty! array, I know maybe it's a asynchronous issue, but I don't know how to use async, await in map any idea?
const [array, setArray] = useState([]);
const fileBase64 = (img) => {
let result = [...img];
setUrlImage(img);
result && result.map(function (img){
let fileReader = new FileReader();
fileReader.readAsDataURL(img);
fileReader.onloadend = async () => {
let res = await fileReader.result;
setArray([...array, res])
};
})
console.log(array)
}
const handleImage = (e) => {
let image = [...e.target.files];
fileBase64(image);
}
<input type="file" multiple={true} onChange={handleImage}/>
Due to this asynchronous nature, state is being set i.e. push before data urls are set in array.
And that's the reason your your array return empty.
To fix it, you can use create Promise which gets resolved after load event of each file. And use Promise.all which would be resolved after each Promise has resolved and then use setArray:
fileBase64 = (img) => {
return new Promise((resolve, reject) => {
let fileReader = new FileReader();
fileReader.onerror = reject
fileReader.onload = function () {
resolve(fileReader.result)
}
fileReader.readAsDataURL(img)
})
}
handleImage = (e) => {
let image = e.target.files;
Promise.all(Array.from(image).map(this.readAsDataURL))
.then((urls) => {
setArray(urls)
})
.catch((error) => {
console.error(error)
})
}
I've obviously got some missing knowledge when it comes to async-await
I expect the base64 for each image to be logged one after another in the same order as the batch list array and then I expect end to be logged.
I'm getting a load of undefined and end logged first! Eeek!
async function GetImages() {
async function blobToBase64(blob) {
const reader = new FileReader();
reader.onloadend = () => {
return reader.result;
};
reader.readAsDataURL(blob);
}
async function getBase64(url) {
const response = await fetch(url);
const blob = await response.blob();
const base64 = await blobToBase64(blob);
return base64
}
async function fetchBatchList() {
const batchList = [
"https://i.imgur.com/M0K21iS.jpg",
"https://i.imgur.com/uNbsNAd.jpg",
"https://i.imgur.com/QdqhGb9.jpg"
];
batchList.forEach(async url => {
const res = await getBase64(url)
console.log(res)
})
}
async function end() {
console.log('end')
}
await fetchBatchList();
await end();
}
GetImages();
Adding async to a function doesn't make it wait for the callback to be called.
You need to wrap your callback from FileReader in a promise to get the result.
You're also not awaiting anything in your fetchBatchList function. A good solution is to just map each item to a promise and then use Promise.all() to wait until all urls are finished.
async function GetImages() {
function blobToBase64(blob) {
// Create promise
return new Promise((resolve, reject) => {
const reader = new FileReader();
// Resolve value when done
reader.onloadend = () => {
resolve(reader.result);
};
// Reject if we have an error
reader.onerror = () => {
reject(reader.error);
}
reader.readAsDataURL(blob);
});
}
async function getBase64(url) {
const response = await fetch(url);
const blob = await response.blob();
const base64 = await blobToBase64(blob);
return base64
}
async function fetchBatchList() {
const batchList = [
"https://i.imgur.com/M0K21iS.jpg",
"https://i.imgur.com/uNbsNAd.jpg",
"https://i.imgur.com/QdqhGb9.jpg"
];
// Map each url to a promise
const list = batchList.map(url => {
return getBase64(url)
})
// Wait until all are done
const urls = await Promise.all(list);
console.log(urls);
return urls;
}
async function end() {
console.log('end')
}
await fetchBatchList();
await end();
}
GetImages();
Here is the code that I tried.
// To get base64 code of file
const toBase64 = file => new Promise((resolve, reject) => {
const reader = new FileReader();
reader.readAsDataURL(file);
reader.onloaded = () => resolve(reader.result.replace(/^data:.+;base64,/, ''));
reader.onerror = error => reject(error);
})
// To make an array of files
const getAttachments = async files => {
let documents;
try {
documents = files.map(async file => {
let base64 = await toBase64(file)
return {
doc: base64,
documentName: file.name,
documentType: file.type
}
})
} catch {
console.error('Failed to get files as base64')
}
return Promise.resolve(documents)
}
And I just tried to get an object array as a result by using the above 2 functions.
Like the following;
getAttachments(Array.from(event.target.files)).then(documents => {
console.info(documents)
}
But the result is
Logged out result in Console
I'd love to know how I can get what I want.
Thanks.
Instead of returning an array of promises try returning an array of resolved promise using the await keyword.
try this
const getAttachments = async files => {
let documents;
try {
documents = files.map(async file => {
let base64 = await toBase64(file)
return {
doc: base64,
documentName: file.name,
documentType: file.type
}
})
return await Promise.all(documents);
} catch {
console.error('Failed to get files as base64')
}
}
I have seen couple of examples here and based on those examples I have prepared my solution. But still I am getting Promise
__proto__: Promise
[[PromiseStatus]]: "pending
[[PromiseValue]]: undefined
I am trying to store my base 64 data into my base64 variable. Please let me know what's wrong with my code and a working solution.
Below is my try:
import React, { Component } from 'react'
import { DropzoneArea, DropzoneDialog } from 'material-ui-dropzone'
import Button from '#material-ui/core/Button';
async function getBaseData(selectedFile) {
let base64;
var fileToLoad = selectedFile[0];
base64 = await getBase64(fileToLoad);
return base64; //This returns me PromiseStatus Pending
}
function getBase64(file) {
return new Promise((resolve, reject) => {
const reader = new FileReader();
reader.readAsDataURL(file);
reader.onload = () => resolve(reader.result);
reader.onerror = error => reject(error);
return Promise.resolve(reader.result)
});
}
export class ImageUploader extends Component {
constructor(props) {
super(props);
this.state = {
files: [],
image: null,
};
}
handleImageChange(files) {
const { imageCallback } = this.props
imageCallback(getBaseData(files)); // By this callback i am trying to pass my base64 string to other component
}
render() {
return (
<DropzoneArea
acceptedFiles={['image/*']}
filesLimit={1}
maxFileSize={10000000}
//showPreviews={false}
onChange={this.handleImageChange.bind(this)}
/>
)
}
}
your problem is how you are managing your Promise. you dont need to return anything from a Promise as the documentation says or it will resolve inmediatly.
in this case when you do:
function getBase64(file) {
return new Promise((resolve, reject) => {
const reader = new FileReader();
reader.readAsDataURL(file);
reader.onload = () => resolve(reader.result);
reader.onerror = error => reject(error);
return Promise.resolve(reader.result) // <--- this is wrong.
});
}
it will resolve to Promise.resolve(reader.result) which is undefined, because maybe it didn't finish, maybe and just maybe you could get an accurate result but it is just luck that the reader actually resolves before that return(race condition)
basically in order to make it work, you need to use the resolver and the rejecter of the promise so it can resolve/reject.
EDIT: I noticed that you are also calling the reader function before setting the callbacks, then you are reading the file, adding the callbacks and then the callbacks are not called.
just change your code to this:
function getBase64(file) {
return new Promise((resolve, reject) => {
const reader = new FileReader();
//set the callbacks before reading the object
reader.onload = () => resolve(reader.result);
reader.onerror = error => reject(error);
reader.readAsDataURL(file);
});
}
The problem is getBaseData is an async function but you still need to wait for it to resolve.
Try "awaiting" for it as well like this:
handleImageChange(files) {
const { imageCallback } = this.props;
async function wrap(){
const result = await getBaseData(files); //result should be a value here and not promise
imageCallback(result);
}
wrap(); // call the async function which will await the getBaseData and use your callback
}
There are two problems here: the creation of the Promise is incorrect, and you don't handle the resolved Promise as you should. There is no need for any async or await like other answers suggest.
The Promise creation:
function getBaseData(selectedFiles) {
var fileToLoad = selectedFiles[0];
return getBase64(fileToLoad);
}
function getBase64(file) {
return new Promise((resolve, reject) => {
const reader = new FileReader();
reader.onload = () => resolve(reader.result);
reader.onerror = error => reject(error);
reader.readAsDataURL(file);
});
}
Resolving the Promise:
handleImageChange(files) {
const { imageCallback } = this.props
getBaseData(files).then(imageCallback);
}
I am using react-native-facebook-login in order to get user data. Its returning me the url of the profile picture. when I paste this url in the browser the picture gets downloaded.
Its returning the following string named profile
{"id":"10210xxx114564932","name":"Stan Shivam","email":"ansh1602#gmail.com","first_name":"Shivam","last_name":"Stan","age_range":{"min":21},"link":"https:\/\/www.facebook.com\/app_scoped_user_id\/10210663114564932\/","picture":{"data":{"height":50,"is_silhouette":false,"url":"https:\/\/lookaside.facebook.com\/platform\/profilepic\/?asid=10210663114564932&height=50&width=50&ext=1523007905&hash=AeQ-_PZnt1JTbnth","width":50}},"gender":"male","locale":"en_GB","timezone":5.5,"updated_time":"2018-03-27T18:37:33+0000","verified":true}
What I want to do is I want to convert the url to base64 and send it to server. But I am not being able to find some good tut on this.
What I tried so far.
getBase64ImageFromUrl = async (imageUrl) => {
const res = await fetch(imageUrl);
console.log(res);
const blob = await res.blob();
return new Promise((resolve, reject) => {
const reader = new FileReader();
reader.addEventListener('load', () => {
resolve(reader.result);
}, false);
reader.onerror = () => {
return reject(this);
};
reader.readAsDataURL(blob);
});
};
calling it from constructor
this.getBase64ImageFromUrl('https://lookaside.facebook.com/platform/profilepic/?asid=10210663114564932&height=50&width=50&ext=1523007763&hash=AeSavHT5oXVEMq4w')
.then(result => console.log(result))
.catch(err => console.error(err));
But it gives me res.blob() is not a function.
What exactly do I need to use in order to achive it.
I resolved this using FileReader. use the following function and pass the url and callback. Thanks
export const toDataUrl = (url, callback) => {
const xhr = new XMLHttpRequest();
xhr.onload = () => {
const reader = new FileReader();
reader.onloadend = () => {
callback(reader.result);
};
reader.readAsDataURL(xhr.response);
};
xhr.open('GET', url);
xhr.responseType = 'blob';
xhr.send();
};
calling the function
toDataUrl('https://lookaside.facebook.com/platform/profilepic/?asid=10210663114564932&height=50&width=50&ext=1523007763&hash=AeSavHT5oXVEMq4w', (myBase64) => {
console.log(myBase64); // myBase64 is the base64 string
});