23
votes

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.

10
When you select multiple files, I would guess e.target.files contains more than one entry? If so, e.target.files.forEach(function(file) { /* Do what you did before to upload each file */ }); - ArneHugo

10 Answers

33
votes

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

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.

13
votes

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

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

@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)
    })
  });
0
votes

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

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.

0
votes

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

0
votes

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