32

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.

isuru
  • 3,385
  • 4
  • 27
  • 62
  • 1
    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 Jan 16 '17 at 09:55
  • If you wish to upload all the files (nested or otherwise) in a folder the solution is here - https://stackoverflow.com/a/69669345/1205871 – danday74 Oct 21 '21 at 22:34

10 Answers10

41

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;
            }
        );
    });
}
isuru
  • 3,385
  • 4
  • 27
  • 62
  • 2
    if you put several concurrent upload task like you did, they will be uploaded sequentially – user1040495 Feb 13 '17 at 20:42
  • @isuru Can you share your html source file? Are you previewing the images? – Menu Mar 11 '17 at 10:30
  • @ArunaRajput I used above method to upload multiple images to Firebase storage. Actually what do you want to achieve? – isuru Mar 13 '17 at 06:22
  • @isuru Could you tell me how the variable imageFile looks like? – Walter Monecke Aug 07 '17 at 11:51
  • 3
    Thank you very much! I spent the whole day trying to understand the whole thing with File objects and your answer helped a lot. The only thing I can think of to add here is the overall upload progress, which isn't that easy to implement here. – Telion Jan 17 '18 at 23:36
  • 1
    @user1040495 That is not true, they upload in parallel – shakirthow Jul 29 '18 at 01:33
  • @user1040495 Yes, so what's the issue? – isuru Jul 29 '19 at 04:32
  • facing (node:18140) UnhandledPromiseRejectionWarning: TypeError: Cannot read property 'byteLength' of undefined ,getting this error because of started file uploading before completion of file,any one know how to handle this? – Balaji Jul 22 '21 at 16:25
  • what if i want all the downloadUrls in an array after all the file uploads are complete. how to achieve this, i tried adding in an array but i only got the last url only as i am calling the service newly for each file – nongjai Oct 14 '21 at 12:43
22

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.

user2875289
  • 2,799
  • 1
  • 22
  • 25
  • 2
    Can you update this to the current Firebase documentation or at least share how to get the progress of the currently running task in order to update the UI accordingly – Taio Jan 26 '20 at 18:29
13

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
});
Mike McDonald
  • 15,609
  • 2
  • 46
  • 49
  • Have not tested yet but seems elegant approach, Is there a way we can keep track of upload progress? instead of using `then/catch` can we use `on`? – Anwer AR Feb 08 '20 at 23:01
3
        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)
        });
Akın Köker
  • 405
  • 5
  • 7
3

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);
            }
        );
    });
}
Taio
  • 3,152
  • 11
  • 35
  • 59
3

@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)
    })
  });
Ashfaq nisar
  • 2,500
  • 1
  • 12
  • 22
2

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.

Dharman
  • 30,962
  • 25
  • 85
  • 135
Hank
  • 1,658
  • 12
  • 13
2

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;
};
Sachin Shukla
  • 335
  • 3
  • 3
0

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);
          });
      }
Hitesh Sahu
  • 41,955
  • 17
  • 205
  • 154
-1

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

Thabo
  • 1,303
  • 2
  • 19
  • 40