How to upload multiple files to Firebase? - javascript

Is there a way to upload multiple files to Firebase storage. It can upload single file within single attempt as follows.
fileButton.addEventListener('change', function(e){
//Get file
var file = e.target.files[0];
//Create storage reference
var storageRef = firebase.storage().ref(DirectryPath+"/"+file.name);
//Upload file
var task = storageRef.put(file);
//Update progress bar
task.on('state_changed',
function progress(snapshot){
var percentage = snapshot.bytesTransferred / snapshot.totalBytes * 100;
uploader.value = percentage;
},
function error(err){
},
function complete(){
var downloadURL = task.snapshot.downloadURL;
}
);
});
How to upload multiple files to the Firebase storage.

I found the solution for my above question and I like to put it here because it can be useful for anyone.
//Listen for file selection
fileButton.addEventListener('change', function(e){
//Get files
for (var i = 0; i < e.target.files.length; i++) {
var imageFile = e.target.files[i];
uploadImageAsPromise(imageFile);
}
});
//Handle waiting to upload each file using promise
function uploadImageAsPromise (imageFile) {
return new Promise(function (resolve, reject) {
var storageRef = firebase.storage().ref(fullDirectory+"/"+imageFile.name);
//Upload file
var task = storageRef.put(imageFile);
//Update progress bar
task.on('state_changed',
function progress(snapshot){
var percentage = snapshot.bytesTransferred / snapshot.totalBytes * 100;
uploader.value = percentage;
},
function error(err){
},
function complete(){
var downloadURL = task.snapshot.downloadURL;
}
);
});
}

Firebase Storage uses Promise, so you can use Promises to achieve it.
Here's the firebase blog article that covers this subject:
Keeping our Promises (and Callbacks)
Give Promise.all() an "Array of Promises"
Promise.all(
// Array of "Promises"
myItems.map(item => putStorageItem(item))
)
.then((url) => {
console.log(`All success`)
})
.catch((error) => {
console.log(`Some failed: `, error.message)
});
Upload each file and return a Promise
putStorageItem(item) {
// the return value will be a Promise
return firebase.storage().ref("YourPath").put("YourFile")
.then((snapshot) => {
console.log('One success:', item)
}).catch((error) => {
console.log('One failed:', item, error.message)
});
}
YourPath and YourFile can be carried with myItems array (thus the item object).
I omitted them here just for readability, but you get the concept.

I believe there's a simpler solution:
// set it up
firebase.storage().ref().constructor.prototype.putFiles = function(files) {
var ref = this;
return Promise.all(files.map(function(file) {
return ref.child(file.name).put(file);
}));
}
// use it!
firebase.storage().ref().putFiles(files).then(function(metadatas) {
// Get an array of file metadata
}).catch(function(error) {
// If any task fails, handle this
});

let ad_images=["file:///data/user/0/..../IMG-20181216-WA00001.jpg",
"file:///data/user/0/..../IMG-20181216-WA00002.jpg",
"file:///data/user/0/..../IMG-20181216-WA00003.jpg"];
let firebase_images=[];
const ref = firebase.firestore().collection('ads').doc(newRecord.id);
putStorageItem = (url,index,ext) => {
return firebase.storage().ref('YOURFOLDER/'+ index +'.'+ext ).putFile(url)
.then((snapshot) => {
console.log(snapshot)
firebase_images[index] = snapshot.downloadURL;
//OR
//firebase_images.push(snapshot.downloadURL);
}).catch((error) => {
console.log('One failed:', error.message)
});
}
Promise.all(
ad_images.map( async (item,index) => {
let ext = item.split('/').pop().split(".").pop();
console.log(newRecord.id, item, index, ext);
await putStorageItem(newRecord.id, item, index, ext);
})
)
.then((url) => {
console.log(`All success`);
console.log(firebase_images);
})
.catch((error) => {
console.log(`Some failed: `, error.message)
});

This is a modification of the marked answer for those looking to wait for each upload to complete before the other starts.
As the marked answer stands, the promise is not resolved or rejected so when the upload begins from the loop everything just starts, the 1st file, 2nd.....
Think of 3 uploads each 20mb. The loop will call the upload function almost at the same time, making them run almost concurrently.
This answer solves this using async/await to handle the promises
fileButton.addEventListener('change', async function(e){
//Get files
for (var i = 0; i < e.target.files.length; i++) {
var imageFile = e.target.files[i];
await uploadImageAsPromise(imageFile).then((res)=>{
console.log(res);
});
}
});
//Handle waiting to upload each file using promise
async function uploadImageAsPromise (imageFile) {
return new Promise(function (resolve, reject) {
var storageRef = firebase.storage().ref(fullDirectory+"/"+imageFile.name);
var task = storageRef.put(imageFile);
//Update progress bar
task.on('state_changed',
function progress(snapshot){
var percentage = snapshot.bytesTransferred / snapshot.totalBytes *
100;
},
function error(err){
console.log(err);
reject(err);
},
function complete(){
var downloadURL = task.snapshot.downloadURL;
resolve(downloadURL);
}
);
});
}

#isuru, the guy who uploaded the question has a great solution provided below. But, some of the firebase functions have been updated. So, I have just updated the solution with the new updates in the Firebase.
//Firebase Storage Reference
const storageRef = firebase.storage().ref();
//Upload Image Function returns a promise
async function uploadImageAsPromise(imageFile) {
return new Promise(function (resolve, reject) {
const task = storageRef.child(imageFile.name).put(imageFile);
task.on(
"state_changed",
function progress(snapshot) {
const percentage = (snapshot.bytesTransferred / snapshot.totalBytes) * 100;
},
function error(err) {
reject(err);
},
async function complete() {
//The getDownloadURL returns a promise and it is resolved to get the image url.
const imageURL = await task.snapshot.ref.getDownloadURL();
resolve(imageURL);
}
);
});
}
//Handling the files
fileButton.addEventListener('change', function(e){
const promises = [];
for(const file of e.target.files){//Instead of e.target.files, you could also have your files variable
promises.push(uploadImageAsPromise(file))
}
//The Promise.all() will stop the execution, until all of the promises are resolved.
Promise.all(promises).then((fileURLS)=>{
//Once all the promises are resolved, you will get the urls in a array.
console.log(fileURLS)
})
});

Upload a file & get download URL
export const handleFileUploadOnFirebaseStorage = async (bucketName, file) => {
// 1. If no file, return
if (file === "") return "";
// 2. Put the file into bucketName
const uploadTask = await storage.ref(`/${bucketName}/${file.name}`).put(file);
// 3. Get download URL and return it as
return uploadTask.ref.getDownloadURL().then((fileURL) => fileURL);
};
Upload multiple files & get download URL
export const handleFilesUploadOnFirebaseStorage = async (bucketName, files) => {
// 1. If no file, return
if (files.length === 0) return [];
// 2. Create an array to store all download URLs
let fileUrls = [];
// 3. Loop over all the files
for (var i = 0; i < files.length; i++) {
// 3A. Get a file to upload
const file = files[i];
// 3B. handleFileUploadOnFirebaseStorage function is in above section
const downloadFileResponse = await handleFileUploadOnFirebaseStorage(bucketName, file);
// 3C. Push the download url to URLs array
fileUrls.push(downloadFileResponse);
}
return fileUrls;
};

all the promises get messy pretty quickly, why not use async and await instead?
Here, I have a function that keep tracks of all the images selected from the input/file control to be uploaded:
let images =[];
let imagePaths=[];
const trackFiles =(e)=>{
images =[];
imagePaths =[];
for (var i = 0; i < e.target.files.length; i++) {
images.push(e.target.files[i]);
}
}
And I have another function that will be triggered by a button that the user will click on when ready to do the actual upload:
const uploadFiles =()=>{
const storageRef = storage.ref();
images.map(async img =>{
let fileRef = storageRef.child(img.name);
await fileRef.put(img);
const singleImgPath = await fileRef.getDownloadURL();
imagePaths.push(singleImgPath);
if(imagePaths.length == images.length){
console.log("got all paths here now: ", imagePaths);
}
})
}
We basically loop through each image and perform the upload, and push the image paths into a separate imagePaths array one by one as each of them gets finished at its own pace, I then grab all the paths once we know they are all done by comparing the length of the images and their final paths.

We can Combine multiple Promises like this
Promise.all([promise1, promise2, promise3]).then(function(values) {
console.log(values);
});
And we can Chain Promise like this
return myFirstPromise.then( (returnFromFirst) => {
//Do something
return secondPromise();
}).then( (returnFromSecond) => {
//Do something
return thirdPromise();
}).then( (returnFromThird) => {
//All Done
}).catch( (e) =>{}
console.error("SOMETHING WENT WRONG!!!");
);
Idea is to combine upload file promises with Promise.all & chain them together to get download URLS after each upload
Promise.all(
//Array.map creates a new array with the results
// of calling a function for every array element.
//In this case Array of "Promises"
this.state.filesToUpload.map(item =>
this.uploadFileAsPromise(item))
)
.then(url => {
console.log(`All success`);
//Handle Success all image upload
})
.catch(error => {
console.log(`Some failed: `, error.message);
//Handle Failure some/all image upload failed
});
//return a promise which upload file & get download URL
uploadFileAsPromise(imageFile) {
// the return value will be a Promise
return storageRef
.child("images/users/" + imageFile.name)
.put(imageFile.file)
.then(snapshot => {
console.log("Uploaded File:", imageFile.name);
return snapshot.ref.getDownloadURL().then(downloadURL => {
//promise inside promise to get donloadable URL
console.log("File available at", downloadURL);
);
});
})
.catch(error => {
console.log("Upload failed:", imageFile.name, error.message);
});
}

This was a breeze implementing with rxjs's switchMap and combineLatest for the Angular fire

Related

Check array after async task finishes

Today I'm having an issue with async task while using JSZIP.
I want to check the array content after the async task executed by JSZip ends.
I have a zip which contains one XML file which I read and get a specific node to store them in another list to later do some stuffs. Well, my issue is that the checking array is called before the XML file is read and, as it is executed before XML file is read, array is empty.
I tried some ways to make it work, but, without success yet.
fileElement.addEventListener('change', (e) => {
try {
var zip = new JSZip();
zip.loadAsync( fileElement.files[0])
.then(function(zip) {
let xmlfiles = []
const _ziptask = async () => {for(let [filename, file] of Object.entries(zip.files)) {
if (filename.includes("file.xml")) {
file.async("string").then(function (data) {
let xmlDoc = new DOMParser().parseFromString(data,"text/xml");
let metaInputs = [...xmlDoc.querySelectorAll("file")];
xmlfiles = metaInputs.filter(_node => null != _node.getAttribute('src'));
console.log("FILE.XML LOOP ENDED")
});
}
}}
async () => {
await _ziptask().then(() => {
console.log("CHECKING FILE.XML ARRAY ")
console.log(xmlfiles)
})
}
}, function() {console.error("ERROR: NOT ZIP FILE")});
} catch (error) {
restoreFileInput("Something went wrong, try it again later")
}
});
Well, basically after testing different things, I reached the goal by using an array of promises and using Promise.all, which basically check that all the promises were resolved successfully.
Its curious that where I read this, the promises are stored in a const declaration instead var or let.
Anyway, if someone want to see the result:
fileElement.addEventListener('change', (e) => {
try {
var zip = new JSZip();
zip.loadAsync( fileElement.files[0])
.then(function(zip) {
let xmlfiles = []
const promises = [];
for(let [filename, file] of Object.entries(zip.files)) {
if (filename.includes("file.xml")) {
promises.push(file.async("string").then(function (data) {
let xmlDoc = new DOMParser().parseFromString(data,"text/xml");
let metaInputs = [...xmlDoc.querySelectorAll("file")];
xmlfiles = metaInputs.filter(_node => null != _node.getAttribute('src'));
console.log("FILE.XML LOOP ENDED")
}));
}
}
Promise.all(promises).then(function () {
console.log("CHECKING FILE.XML ARRAY ")
console.log(xmlfiles)
});
}, function() {console.error("ERROR: NOT ZIP FILE")});
} catch (error) {
restoreFileInput("Something went wrong, try it again later")
}
});
Thanks for the help to the guys who commented previously.
Best regards.

Execute code only after a Javascript forEach/map

I have a forEach function that loops through an array of files, uploads the file to Firebase Storage and then after the upload, it stores the image reference link into a array defined before.
Process of each iteration:
Take file object and convert to Blob
Upload Blob onto Firebase
After Blob uploads, receive a callback which has the image reference of the upload
Store the image reference in a list
After the forEach, I use the list of image references for further processing. However, when I try and reference the list, it is usually empty because this code executes before any images upload (and references are not recieved). How can I use async/await with a Javascript forEach function where I can invoke my code to wait where it should expect a populated image reference list. I've already tried to find a solution on stackoverflow but hasn't helped toward my solution.
submitPost = async event => {
event.preventDefault();
const img_names = [];
this.state.files.forEach((ref_, index) => {
ref_.current.getCroppedCanvas().toBlob(blob => {
var uploadTask = this.storageRef
.child("images/" + String(index))
.put(blob);
uploadTask.on(
"state_changed",
snapshot => {
var progress =
(snapshot.bytesTransferred / snapshot.totalBytes) * 100;
console.log("Upload is " + progress + "% done");
},
error => {
console.log(error);
},
() => {
img_names.push(uploadTask.snapshot.ref.toString());
}
);
});
console.log(img_names) // empty here
});
You can achieve that functionality by using map instead of forEach. Convert your array into an Array of Promises. Then use that array inside a Promise.all() and await it.
const promisifyUpload = (storageRef, ref_, index) => {
return new Promise((resolve, reject) => {
ref_.current.getCroppedCanvas().toBlob(blob => {
var uploadTask = storageRef
.child("images/" + String(index))
.put(blob);
uploadTask.on(
"state_changed",
snapshot => {
var progress = (snapshot.bytesTransferred / snapshot.totalBytes) * 100;
console.log("Upload is " + progress + "% done");
},
error => console.log(error),
() => resolve(uploadTask.snapshot.ref.toString())
);
});
});
};
const submitPost = async event => {
event.preventDefault();
const img_names = await Promise.all(
this.state.files.map((ref_, index) => promisifyUpload(this.storageRef, ref_, index))
));
console.log(image_names); // Should now be filled
});
Edit: I split it up into 2 functions to show isolate the "promisify" logic

call then only after method returning promise is finished

submitTCtoDB() {
console.log("this.selectedFileList is: " + this.selectedFileList)
this.readFile().then(() => {
alert("ReadFile Finished now submit TC");
this.submitTC()
});
}
readFile() {
return new Promise((resolve, reject) => {
for (let i = 0; i < this.selectedFileList.length; i++) {
let file = this.selectedFileList[i];
alert("file in redafile" + file.name)
let fileReader = new FileReader();
fileReader.onload = () => {
this.fileContent = fileReader.result;
if (this.fileContent.indexOf("END DATA | BEGIN RESULTS") != -1) {
alert("Multiple testcases found in " + file.name + " file. Please separate/save testcases in Calc Builder. Then reimport");
const index: number = this.selectedFileList.indexOf(file);
if (index > -1) {
this.selectedFileList.splice(index, 1);
}
console.log(this.fileContent);
}
resolve(this.fileContent);
}
fileReader.readAsText(file);
}
});
}
I want to run the submitTC() method only after the readFile method is completely finished but .then(inside submitTCtoDB) is getting invoked early .
I think .then or promise is not used properly.
Desired functionality is to call the submitTC method only when readFile method is completed reading/splicing the files.
Kindly help.
You have a resolve call in a loop, but resolve only has an effect when called the first time: once a promise resolves, that is its final state, and the then callbacks are triggered. So that happens when the first file has been read, without waiting for any other files to be processed.
What you could do:
Promisify the FileReader without adding specific logic (your if check): keep that outside of it, so it remains generic
Use Promise.all to map the file list to a new promise that will give the list of file contents.
Process the list of contents for the specific checks
Return the new promise (Promise.all or the one chained on it) to the caller.
Code:
submitTCtoDB() {
console.log("this.selectedFileList is: " + JSON.stringify(this.selectedFileList))
this.readFileList(this.selectedFileList).then((validList) => {
alert("ReadFile Finished now submit TC");
this.selectedFileList = validList;
this.submitTC()
});
}
readFileList(list) {
return Promise.all(list.map(file => this.readFile(file))).then(contents => {
return list.filter((file, i) => {
const fileContent = contents[i];
if (fileContent.indexOf("END DATA | BEGIN RESULTS") != -1) {
console.log("Multiple testcases found in " + file.name + " file. Please separate/save testcases in Calc Builder. Then reimport");
console.log(fileContent);
return false; // exclude this file
}
return true; // include this file
});
});
}
readFile(file) {
return new Promise(resolve => {
console.log("file in promiseFile: " + file.name);
const fileReader = new FileReader();
fileReader.onload = () => resolve(fileReader.result);
fileReader.readAsText(file);
});
}

Push into an array from foreach and make it available outside foreach

I stuck by looping through an array that receive values from a promise and push values into a new array which is available outside the foreach.
What i have:
app.post('/submit', function (req, res) {
uploadPics(req, res, function (err) {
if (err instanceof multer.MulterError) {
res.send(JSON.stringify({UploadResult: err.message}));
console.log(err.message + ' ' +'Redirect /home');
} else if (err) {
console.log(err);
} else {
res.send(JSON.stringify({UploadResult: 'Success'}));
var filesarray = req.files;
var picinfos = [];
filesarray.forEach(function(file){
GetFileMetaInfo.filemetainfo(file.path).then(function (metadata){
//Stuck here! Can push values into an array (picinfos) but only available in the foreach. not outside..
})
})
//I need picinfos array here....
}
})
})
How i receive my metadata:
var exif = require('exif-parser');
var fs = require('fs');
exports.filemetainfo = function (filepath) {
return new Promise((resolve) => {
var file = filepath;
var buffer = fs.readFileSync(file);
var parser = exif.create(buffer);
var result = parser.parse();
resolve (result);
}).then(function (metadata){
if (metadata.tags.CreateDate !== undefined){
date = new Date (metadata.tags.CreateDate*1000);
datevalues = [
date.getFullYear(),
date.getMonth()+1,
date.getDate(),
date.getHours(),
date.getMinutes(),
date.getSeconds(),
];
CreateDate = date.getFullYear()+'-'+(date.getMonth()+1)+'-'+date.getDate();
CreateTime = date.getHours()+':'+date.getMinutes()+':'+date.getSeconds();
console.log("CrDate:" +CreateDate, "CrTime:" +CreateTime );
} else {
console.log("No Metadata Creation Infos found in " +filepath);
CreateDate = "";
CretaeTime = "";
}
if (metadata.tags.GPSLatitude !== undefined){
GPSLat = metadata.tags.GPSLatitude;
GPSLon = metadata.tags.GPSLongitude;
console.log("GPSLat:" + GPSLat , "GPSLon:" +GPSLon);
}
else {
console.log("No Metadata GPS Infos found in " +filepath)
GPSLat = "";
GPSLon = "";
}
return MetaData = {
GPSLat: GPSLat ,
GPSLon: GPSLon,
CreateDate: CreateDate,
CreateTime: CreateTime,
}
})
}
May i ask someone to give a hand. How can i make my array available outside the foreach. thank you very much!
The reason you're getting empty array at the end of forEach is because, GetFileMetaInfo.filemetainfo() returns a promise and forEach won't wait for async actions.
You could use async/await with for...of loop to get your desired result.
app.post('/submit', function (req, res) {
uploadPics(req, res, async function (err) { // note async here
if (err instanceof multer.MulterError) {
res.send(JSON.stringify({UploadResult: err.message}));
console.log(err.message + ' ' +'Redirect /home');
} else if (err) {
console.log(err);
} else {
res.send(JSON.stringify({UploadResult: 'Success'}));
var filesarray = req.files;
var picinfos = [];
for(let file of filesarray) {
const metadata = await GetFileMetaInfo.filemetainfo(file.path);
// push metadata into your array here
picinfos.push(metadata);
}
// You will have picinfos here
}
})
})
Although the question is already answered by Dinesh Pandiyan there are still some adjustments that can be made. The following code in his answer runs sequential, meaning that every async request is made after the previously returned result is resolved.
for(let file of filesarray) {
const metadata = await GetFileMetaInfo.filemetainfo(file.path);
// ^- pauses the execution of the current running code
// push metadata into your array here
picinfos.push(metadata);
}
async call #1 ╌╌await╌╌> async call #2 ╌╌await╌╌> async call #3 ╌╌await╌╌> result
You could make the code concurrent by first executing all async statements and then wait until all results are resolved. This can be done by simply changing the following:
// execute all the async functions first, reducing the wait time
for(let file of filesarray) {
const metadata = GetFileMetaInfo.filemetainfo(file.path);
// ^- remove the await
// push metadata into your array here
picinfos.push(metadata);
}
// wait for all results to be resolved
picinfos = await Promise.all(picinfos);
// ^- instead await here
async call #1 ╌╌┐
async call #2 ╌╌┼╌╌await all╌╌> result
async call #3 ╌╌┘
The above could be further simplified by simply using an Array.map() in combination with the already shown Promise.all().
var filesarray = req.files;
var picinfos = await Promise.all(filesarray.map(file => {
return GetFileMetaInfo.filemetainfo(file.path);
}));
// picinfos should be present
Or if you want to avoid working with async/await:
var filesarray = req.files;
Promise.all(filesarray.map(file => {
return GetFileMetaInfo.filemetainfo(file.path);
})).then(picinfos => {
// picinfos should be present
});

Javascript Loop doesn't wait for Firebase to complete uploading multiple files

I'm trying to upload multiple files and then execute some code after all the files are uploaded but my loop doesn't wait until each upload completes.
Iterating files:
//files is an array of local files from a <input type="file">
for (var i = 0, f; f = files[i]; i++) {
var imageFile = files[i];
var response = uploadImageAsPromise(imageFile);
}
//after completing all uploads, execute some code here
console.log("Finished uploading all files");
Upload function:
function uploadImageAsPromise(imageFile) {
var image_id = new Date().valueOf();
var storageRef = firebase.storage().ref().child(image_id + "");
return new Promise(function (resolve, reject) {
//Upload file
var task = storageRef.put(imageFile);
//Update progress bar
task.on('state_changed',
function progress(snapshot) {
var percentage = snapshot.bytesTransferred / snapshot.totalBytes * 100;
},
function error(err) {
console.log(err);
},
function complete() {
task.snapshot.ref.getDownloadURL().then(function (downloadURL) {
var picture = downloadURL;
console.log("Finished uploading file: " + image_id);
});
}
);
});
}
However, my 1st console log gets executed even before all files are uploaded. How do I made it wait until everything is done?
The first problem is your uploadImageAsPromise function. It never actually resolves the promise it creates, so anything that is waiting on the result will wait forever.
This should presumably work:
function uploadImageAsPromise(imageFile) {
var image_id = new Date().valueOf();
var storageRef = firebase.storage().ref().child(image_id + "");
return new Promise(function (resolve, reject) {
//Upload file
var task = storageRef.put(imageFile);
//Update progress bar
task.on('state_changed',
function progress(snapshot) { },
function error(err) { reject(err); },
function complete() { resolve(task); }
);
}).then(function (task) {
return task.snapshot.ref.getDownloadURL();
}).then(function (downloadURL) {
console.log("Finished uploading file: " + image_id);
return downloadURL;
});
}
You're not doing anything to wait until the promises complete, so naturally, the next code will continue executing.
To upload the files in parallel and wait until they're done
With async/await (if your execution environment supports it):
async function uploadFiles(files) {
await Promise.all(files.map(uploadImageAsPromise));
}
async function someOuterFunction(files) {
await uploadFiles(files);
console.log('All done!');
}
someOuterFunction(files);
Without async/await:
Promise.all(files.map(uploadImageAsPromise))
.then(function () {
console.log('All done!');
});
To upload the files sequentially and wait until they're done
With async/await (if your execution environment supports it):
async function uploadFiles(files) {
for (let file of files) {
await uploadImageAsPromise(file);
}
}
async someOuterFunction(files) {
await uploadFiles(files);
console.log('All done!');
}
someOuterFunction(files);
Without async/await:
files
.reduce(function (acc, file) {
return acc.then(function () { return uploadImageAsPromise(file); });
}, Promise.resolve())
.then(function () { console.log('All done!'); });
You need to wait for all of the promises to resolve. You can do this concurrently using Promise.all (docs here).
var promises = [];
for (var i = 0, f; f = files[i]; i++) {
var imageFile = files[i];
promises.push(uploadImageAsPromise(imageFile));
}
Promise.all(promises).then(function(values) {
//after completing all uploads, execute some code here
console.log("Finished uploading all files", values);
}).catch(function(error) {
console.log("One of the promises rejected.", error);
});

Categories

Resources