JavaScript Promise with FileReader - javascript

I'm trying to return a split array when a user specifies a text file in an input box using promises, but the console.log keeps returning undefined, despite resolve actually finding the data.
I know im using the promise wrong but i just can't figure it out, any help would be very appreciated
class TextReader {
readFile (event) {
let file = event.target.files[0]
var promise = Promise.resolve()
pFileReader(file)
promise.then(function (result) {
console.log(result)
})
function pFileReader (file) {
return new Promise((resolve, reject) => {
var reader = new FileReader()
reader.onload = function found () {
resolve(reader.result)
}
reader.readAsText(file)
})
}
}
}
This is the code in my html
<input type='file' accept='text/plain' id="file" onchange='ValidateInput(event)'/>
function ValidateInput (event) {
let myTextReader = new TextReader()
let output = myTextReader.readFile(event)
}

A promise is returned by pFileReader and you need to resolve the returned Promise and not a new Promise
class TextReader {
readFile (event) {
let file = event.target.files[0]
var promise = pFileReader(file)
promise.then(function (result) {
console.log(result)
})
function pFileReader (file) {
return new Promise((resolve, reject) => {
var reader = new FileReader()
reader.onload = function found () {
resolve(reader.result)
}
reader.readAsText(file)
})
}
}
}

Related

Blob to Base64 in javascript not returning anything from FileReader

I am using FileReader in typescript to convert a blob to a base64 image that will then be displayed in the template of my application.
adaptResultToBase64(res: Blob): string {
let imageToDisplay : string | ArrayBuffer | null = '';
const reader = new FileReader();
reader.onloadend = function () {
imageToDisplay = reader.result;
return imageToDisplay;
};
reader.readAsDataURL(res);
return imageToDisplay;
}
Whilst the data logged inside the read.onloadend function displays the base64 string I cannot pass it out of the function.
I have tried adding a callback but where it is called elsewhere doesn't return anything but an empty string.
Please check this code
<input type="file" id="file">
<button id="click">click</button>
let data: string | ArrayBuffer;
document.getElementById('file').onchange = function (e: Event) {
let files: FileList | null = (<HTMLInputElement>e.target).files;
let reader: FileReader = new FileReader();
reader.onload = function (e: ProgressEvent<FileReader>) {
console.log(e.target.result);
data = e.target.result;
};
if (files.length > 0) {
reader.readAsDataURL(files?.[0]);
}
};
document.getElementById('click').onclick = function () {
console.log(data); // result if present otherwise null is returned
};
Using a separate method view. The return value is a Promise.
function adaptResultToBase64(res: Blob): Promise<string> {
let reader: FileReader = new FileReader();
return new Promise((resolve, reject) => {
reader.onloadend = () => {
resolve(reader.result as string);
}
reader.onerror = () => {
reject("Error reading file.");
}
reader.readAsDataURL(res);
})
}
To get the result
adaptResultToBase64(/* Blob value */)
.then(resp => console.log(resp))
.catch(error => console.log(error));
See here for specifics on Promise
MDN
learn.javascript.ru
The basic result I needed and did not realise that the reader.onload is actually a callback for read.readAsDataUrl and finishes everything inside it async.
adaptResultToBase64(res:Blob){
const reader = new FileReader();
reader.onload = function () {
// Was missing code here that needed to be called asynchronously.
adapterToNewObject(reader.result.toString())
};
reader.readAsDataURL(res);
}
}
I was performing this in Angular so for anyone else who runs into this problem here it is using Angular syntax:
In your class:
export class Component {
adaptedResult:Result
getBase64() {
this.http.get().subscribe((result: Blob) => {
const reader = new FileReader();
reader.onload = () => {
this.adaptedResult = this.adapter(reader.result) // Assign or use reader.result value, this is an example of using an adapter function.
};
reader.readAsDataURL(result);
});
}
adapter(base64:string){
return {
name:'image',
image:base64'
}
}
}

Problem with async/await with FileReader in JavaScript

I'm using FileReader in a Vue.js project, and I have a problem with this code:
async uploadDocuments(files) {
for (let file of files) {
let fileName = file.name;
let fileContent;
let reader = new FileReader();
reader.onload = async () => {
fileContent = reader.result;
await this.submitFile(fileContent, fileName, fileType)
.then(/* THEN block */)
.catch(/* CATCH block */);
};
reader.onerror = (error) => { console.warn(error); };
reader.readAsDataURL(file);
}
console.log("COMPLETED");
}
async submitFile(fileContent, fileName, fileType) {
// this function should handle the file upload and currently contains a timeout
await new Promise((resolve) => setTimeout(resolve, 3000));
}
This is the desired execution order (example with two files):
(wait 3s)
THEN block (file 1)
(wait 3s)
THEN block (file 2)
COMPLETED
But this is the actual execution order:
COMPLETED
(wait 3s)
THEN block
THEN block
The "THEN block" is correctly executed after the timeout, but the execution of the code in the for loop continues without waiting the execution of the onload function.
How can I make the reader asynchronous? I tried many solutions (such as wrapping the for loop in a promise and putting the resolve() function inside .then()) but none of them works.
I'd recommend to "promisify" the Reader thing and then use Promise.all until all the files are uploaded.
uploadDocuments = async (event, files) => {
const filePromises = files.map((file) => {
// Return a promise per file
return new Promise((resolve, reject) => {
const reader = new FileReader();
reader.onload = async () => {
try {
const response = await this.submitFile(
reader.result,
file.name,
fileType
);
// Resolve the promise with the response value
resolve(response);
} catch (err) {
reject(err);
}
};
reader.onerror = (error) => {
reject(error);
};
reader.readAsDataURL(file);
});
});
// Wait for all promises to be resolved
const fileInfos = await Promise.all(filePromises);
console.log('COMPLETED');
// Profit
return fileInfos;
};
Try this.
Add await before the reader.onload it will hold until then or catch block execute successfully
async uploadDocuments(event) {
for (let file of files) {
let fileName = file.name;
let fileContent;
let reader = new FileReader();
await reader.onload = async () => {
fileContent = reader.result;
await this.submitFile(fileContent, fileName, fileType)
.then(/* THEN block */)
.catch(/* CATCH block */);
};
reader.onerror = (error) => { console.warn(error); };
reader.readAsDataURL(file);
}
console.log("COMPLETED");
}
async submitFile(fileContent, fileName, fileType) {
// this function should handle the file upload and currently contains a timeout
await new Promise((resolve) => setTimeout(resolve, 3000));
}```

Check when all images are done uploading?

I have an array of objects. In each object there are an array of files..
Im looping through all files and uploading them one by one, which works as expected.. However, i want to show a "success" modal when all files from each object are done uploading..
Im struggling a bit here... The code I have so far:
Im thinking im doing something wrong when I do the check on the:
if (allFiles.length === filesToQuestions.length) {
triggerUploadFiles() {
let allFiles = [];
let filesToQuestions = this.filesToQuestions;
filesToQuestions.forEach((item) => {
let files = item.images;
let payload = {
instanceId: item.instanceId,
answerId: item.answerId,
path: item.path,
fileType: item.fileType,
optionId: item.optionId
};
if (files.length > 0) {
files.map(async(file) => {
let reader = new FileReader();
reader.readAsDataURL(file);
reader.onload = async(e) => {
// Make a fileInfo object for storing later
let fileInfo = {
questionId: this.questionId,
name: file.name,
type: file.type,
data: e.target.result,
file: file
};
fileInfo.type.includes('video') ? fileInfo.type = 'video' : fileInfo.type = 'image';
if (payload.instanceId && payload.path) {
const {response} = await uploadFileToCloudinary(fileInfo.data, payload);
try {
this.$store.dispatch('surveys/submitFilesToSurvey', {
instanceId: payload.instanceId,
answerId: payload.answerId,
fileName: response.public_id,
type: fileInfo.type,
optionId: payload.optionId
}).then((response) => {
console.log('file submitted', response);
allFiles.push(fileInfo);
});
} catch (e) {
console.log('could not upload file');
}
}
// If all files have been proceed
if (allFiles.length === filesToQuestions.length) {
const delayForCompletedStatus = 2000;
deletePendingSurveyByID(this.tempSurveyId);
setTimeout(() => {
this.isUploadingFiles = false;
this.$store.dispatch('modals/toggleModal', 'showModalSurveyCreated');
}, delayForCompletedStatus);
}
};
});
}
});
}
You can use Promise.all() to check if all asynchronous events are finished.
The Promise.all() method returns a single Promise that fulfills when all of the promises passed as an iterable have been fulfilled or when the iterable contains no promises. It rejects with the reason of the first promise that rejects.
Promise.all(files.map((file) => {
return new Promise((resolve, reject) => {
...
reader.onload = e => {
// upload file
...
resolve();
}
...
});
})
.then() {
// all asynchronous events are finished!
}
FYI, I added a simple example of using Promise.all.
const promise1 = new Promise((resolve, reject) => {
setTimeout(() => resolve('first'), 1000);
});
const promise2 = new Promise((resolve, reject) => {
setTimeout(() => resolve('second'), 2000);
});
const promiseList = [promise1, promise2];
Promise.all(promiseList).then(function(values) {
console.log(values);
});

promise with FileReader does not give desired deferred result

After using an file input element I would like upload selected files subsequently. Reading the files with FileReader is asynchronous so I tried to defer the upload function call with a promise. It does not work however as the vm.upload() gets called when vm.files array is 'not yet filled'. There is no error from the promise by the way.
Why does the promise not 'wait/ defer'? It might be since I should make a promise closer to the async code (in the map method), but I am not sure why?
let filesPromise = inputFiles => {
return new Promise((resolve, reject) => {
inputFiles.filter(file => !this.queue.some(f => file.name === f.name))
.map(file => {
file.__size = humanStorageSize(file.size)
if (this.noThumbnails || !file.type.startsWith('image')) {
this.queue.push(file)
}
else {
const reader = new FileReader()
reader.onload = (e) => {
let img = new Image()
img.src = e.target.result
file.__img = img
this.queue.push(file)
this.__computeTotalSize()
}
reader.readAsDataURL(file)
}
return file
})
resolve(inputFiles)
reject(new Error('An error occurred in filesPromise'))
})
}
filesPromise(eventFiles)
.then(eventFiles => vm.files.concat(eventFiles))
.then(() => vm.upload())
.catch(error => console.log('An error occurred: ', error))
As #baao commented, a map method containing async code (FileReader event) will just continue and not wait. To solve this the FileReader needs to be put into a Promise and to keep using map with Promises one should build an array consisting of Promise-elements. On this array you can subsequently run Promise.all. This code works:
let filesReady = [] // List of image load promises
files = inputFiles.filter(file => !this.queue.some(f => file.name === f.name))
.map(file => {
initFile(file)
file.__size = humanStorageSize(file.size)
file.__timestamp = new Date().getTime()
if (this.noThumbnails || !file.type.startsWith('image')) {
this.queue.push(file)
}
else {
const reader = new FileReader()
let p = new Promise((resolve, reject) => {
reader.onload = (e) => {
let img = new Image()
img.src = e.target.result
file.__img = img
this.queue.push(file)
this.__computeTotalSize()
resolve(true)
}
reader.onerror = (e) => {
reject(e)
}
})
reader.readAsDataURL(file)
filesReady.push(p)
}
return file
})
if (files.length > 0) {
vm.files = vm.files.concat(files)
Promise.all(filesReady)
.then(() => vm.upload())
.catch(error => console.log('An error occurred: ', error))
}

For what reasons can reader.readAsArrayBuffer fail?

I am using the Filereader
const arrayBufferPromiseFromBlob = function (blob) {
//argument must be blob or file Object
return new Promise(function (resolve, reject) {
const reader = new FileReader();
reader.onload = function (event) {
resolve(reader.result);
};
reader.onerror = function (error) {
reject(error);
};
reader.readAsArrayBuffer(blob);
});
};
sometimes it rejects (onerror), even though it was given a valid blob, what can be the reason this happens ?
In the calling code using arrayBufferPromiseFromBlob , attach a .catch() to the promise and log the error.

Categories