Check when all images are done uploading? - javascript

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

Related

Node.JS: how to wait for a process to finish before continuing?

I am new to node and stuck with this issue. Here' the file:
I am running 'startProcess' function and I want to run 'downloadFiles' and wait until it's completed and save the files before executing any code after it.
This code always ends up running 'runVideoUploadEngine' even before the download has been completed?
const downloadAndSaveFiles = async ({ url, dir }) => {
try {
https.get(url, (res) => {
// File will be stored at this path
console.log('dir: ', dir);
var filePath = fs.createWriteStream(dir);
res.pipe(filePath);
filePath.on('finish', () => {
filePath.close();
console.log('Download Completed');
});
});
return true;
} catch (e) {
console.log(e);
throw e;
}
};
const downloadFiles = async ({ data }) => {
try {
mediaUrl = data.mediaUrl;
thumbnailUrl = data.thumbnailUrl;
const mediaExt = path.extname(mediaUrl);
const thumbExt = path.extname(thumbnailUrl);
mediaDir = `${__dirname}/temp/${'media'}${mediaExt}`;
thumbDir = `${__dirname}/temp/${'thumb'}${thumbExt}`;
await downloadAndSaveFiles({ url: mediaUrl, dir: mediaDir });
await downloadAndSaveFiles({ url: thumbnailUrl, dir: thumbDir });
return { mediaDir, thumbDir };
} catch (e) {
console.log(e);
throw e;
}
};
module.exports = {
startProcess: async ({ message }) => {
//check if message is proper
data = JSON.parse(message.Body);
//download video and thumbnail and store in temp.
console.log('starting download..');
const { mediaDir, thumbDir } = await downloadFiles({ data });
console.log('dir:- ', mediaDir, thumbDir);
pageAccessToken =
'myRandomToken';
_pageId = 'myRandomPageID';
console.log('running engine');
await runVideoUploadEngine({ pageAccessToken, _pageId, mediaDir, thumbDir });
//start videoUploadEngine
//on success: delete video/thumbnail
},
};
What am I doing wrong?
downloadAndSaveFiles returns a promise (because the function is async) but that promise doesn't "wait" for https.get or fs.createWriteStream to finish, and therefore none of the code that calls downloadAndSaveFiles can properly "wait".
If you interact with callback APIs you cannot really use async/await. You have to create the promise manually. For example:
const downloadAndSaveFiles = ({ url, dir }) => {
return new Promise((resolve, reject) => {
// TODO: Error handling
https.get(url, (res) => {
// File will be stored at this path
console.log('dir: ', dir);
var filePath = fs.createWriteStream(dir);
filePath.on('finish', () => {
filePath.close();
console.log('Download Completed');
resolve(); // resolve promise once everything is done
});
res.pipe(filePath);
});
});
};

how to turn multiple files into base64 string?

i have a component like this
<input type="file" multiple #change="toBase64Handler($event)">
<script>
data() {
return {
files: [],
}
},
methods: {
tobase64Handler(event) {
// the code
}
}
</script>
and i would like to turn all of the selected files into base64 string something like this:
files: [
{
selectedFile: 'ajsdgfauywdljasvdajsgvdasdo1u2ydfouayvsdlj2vo8ayasd...'
},
{
selectedFile: 'askdhgoiydvywdljasvdajsgvdasdo1u2ydfoakjgsfdjagswsd...'
},
{
selectedFile: '12edashjvlsljasvdajsgvdasdo1u2ydfouayvsdlj2vo8ayfsd...'
},
]
how do i achieve that?
You can loop though the files call a helper method toBase64, push all Promises to an array and resolve all of them:
toBase64(file) {
return new Promise((resolve, reject) => {
const reader = new FileReader();
reader.readAsDataURL(file);
reader.onload = () => resolve(reader.result);
reader.onerror = error => reject(error);
});
};
async tobase64Handler(files) {
const filePathsPromises = [];
files.forEach(file => {
filePathsPromises.push(this.toBase64(file));
});
const filePaths = await Promise.all(filePathsPromises);
const mappedFiles = filePaths.map((base64File) => ({ selectedFile: base64File }));
return mappedFiles;
}
this should do the trick: JSON.stringify(files) you can read more about it here. If you later want to turn it back into the original array, object, or value then you'd do JSON.parse(files). you can read more about it here
UPDATE: turns out I was wrong and JSON.stringify/parse don't work with files(thanks for the info #patrick evans).
found this answer which seems better (the one by #ahmed hamdy)

Lag by taking an instance of an array using es6

I got an empty array while I'm logging for an instance of an array!
onSelectMultipleImage = (event): Promise<any> => {
return new Promise(resolve => {
const files = <File[]>event.target.files;
let file: File;
let counter = -1;
const response = [];
while (file = files[++counter]) {
const reader: FileReader = new FileReader();
reader.onload = ((f) => {
return () => {
response.push({
file: f,
base64: reader.result
})
}
})(file);
reader.readAsDataURL(file);
console.log(counter);
if(counter == files.length -1) {
console.log('inside the while');
resolve(response);
}
}
});
};
onImagesSelect = async (event: Event) => {
this.newImages = await this.helper.onSelectMultipleImage(event) || [];
console.log(this.newImages, "Original"); // [file: File, base64: "base 64 long string..."]
console.log([...this.newImages],"instance"); // [] empty array
setTimeout(() => {console.log([...this.newImages, 'instance'])}, 2000); // [file: File, base64: "base 64 long string..."]
}
So why I'm getting the presented result? It's something causing by the base64 presented inside the array? if yes what is the solution?
It doesn't wait reader.onload to be completed. So resolve(response) is called before response.push.
You can create a promise to read one file and return them with Promise.all like following code.
readFile = (file: File): Promise<any> => {
return new Promise(resolve => {
const reader: FileReader = new FileReader();
reader.onload = (f) => {
resolve({
file: f,
base64: reader.result
});
};
reader.readAsDataURL(file);
})
}
onSelectMultipleImage = (event): Promise<any> => {
const files = <File[]>event.target.files;
return Promise.all(files.map(file => readFile(file)));
};

JavaScript Promise with FileReader

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

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

Categories