ReactJS upload multiple image in base64 into an array - javascript

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

Related

Having trouble getting a Base64 string from FileReader

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()
// ...

How to fetch images from a string array and then get the base 64 from the file reader with async await?

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

I need help in using double Promises in Javascript

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

How can I use a for loop to reiterate a promise function?

I have the following code that is used to get JSON data from an Amazon Web Server API.
var json1 = new Promise((resolve, reject) => {
fetch(url[0])
.then(r => {
resolve(r.json())
})
.catch(err => {
reject(err)
})
})
I have this repeating 14 times using different urls and json vars and have it return the promises at the end using.
return Promise.all([json1,json2,json3,json4,json5,json6,json7,json8,json9,json10,json11,json12,json13,json14]).then(function(values) {
return values;
});
This works, but it takes up 150+ lines. I want to make a for loop that runs through the same code using a for loop. I created this...
for(var jsonCount = 0;jsonCount<url.length-1;jsonCount++){
jsonArr[jsonCount] = new Promise((resolve, reject) => {
fetch(url[jsonCount])
.then(r => {
resolve(r.json())
})
.catch(err => {
reject(err)
})
})
}
This doesn't work because the promise functions come back as undefined even though it is called by an await function.
const data = await fetchURL(urlToQuery())
Does anyone have suggestions to make this work? There is JSON being returned.
Thanks for your help.
Edit: Here is a larger chunk of the code.
function fetchURL(urls) {
let fetchJson = url => fetch(url).then(response => response.json());
Promise.all(urls.map(fetchJson)).then(arr => {
return arr;
});
(async function() {
const data = await fetchURL(urlToQuery())
console.log(data);
for(var r=0;r<numStations;r++){
if (data[r] == ""){
onlineArr[r] = false;
wdDataArr[r].push(cardinalToDeg(stationHistAvgArr[r]));
wsDataArr[r].push(0);
You can use .map for the loop. But don't use new Promise. You don't need a new promise when fetch already provides you with one.
Also, call your array urls instead of url. A plural will be a good indication for the reader of your code that indeed it is a collection of URLs.
Here is how it could look:
let fetchJson = url => fetch(url).then(response => response.json());
Promise.all(urls.map(fetchJson)).then(arr => {
// process your data
for (let obj of arr) {
console.log(obj);
}
});
I think this example can helps you:
// Mock async function
const getDataAsync = callback => {
setTimeout(
() => callback(Math.ceil(Math.random() * 100)),
Math.random() * 1000 + 2000
)
}
// Create the promise
const getDataWithPromise = () => {
return new Promise((resolve, reject) => {
try {
getDataAsync(resolve);
} catch(e) {
reject(e);
}
});
}
// Using the promise one time
getDataWithPromise()
.then(data => console.log("Simple promise:",data))
.catch(error => console.error(`Error catched ${error}`));
// Promises compound: Promise.all
const promise1 = getDataWithPromise();
promise1.then(data => console.log("promise1 ends:",data));
const promise2 = getDataWithPromise();
promise2.then(data => console.log("promise2 ends:",data));
const promise3 = getDataWithPromise();
promise3.then(data => console.log("promise3 ends:",data));
const promise4 = getDataWithPromise();
promise4.then(data => console.log("promise4 ends:",data));
const promise5 = getDataWithPromise();
promise5.then(data => console.log("promise5 ends:",data));
Promise.all([promise1,promise2,promise3,promise4,promise5])
.then(data => console.log("Promise all ends !!",data));
Hope this helps
you will have issues with closure and var variable capture.
You may want to change var to let to capture the right value in the closure so that url[jsonCount] is actually what you want.
also I think it would be much easier to do something like that in one line :)
let results = [];
for(let i = 0; i < urls.length; ++i) results.push(await (await fetch[urls[i]]).json());
This is a good use for map, mapping urls to promises...
function fetchUrls(urls) {
let promises = urls.map(url => fetch(url))
return Promise.all(promises).then(results => {
return results.map(result => result.json())
})
}}
// url is your array of urls (which would be better named as a plural)
fetchUrls(url).then(results => {
// results will be the fetched json
})
Using the async/await syntax (equivalent meaning)
// this can be called with await from within another async function
async function fetchUrls(urls) {
let promises = urls.map(url => fetch(url))
let results = await Promise.all(promises)
return results.map(result => result.json())
}

Save base64 data into a variable

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

Categories

Resources