Search code examples
javascriptfirebasefirebase-realtime-databasefirebase-storagees6-promise

Firebase web: upload multiple files to Storage an then download their URLs


I am creating a blogging website, and am writing code that does the following in order:
1. stores multiple photos that the user uploads
2. download their URLs
3. save them to the realtime database.

I wrote the function below to do #1 and #2. Basically the idea is to store all the urls to an array, in this case 'urlarray'.

function url_array_get(){
return new Promise(function(resolve,reject){
    let filez=review_photo.files;
    let urlarray=[];
    let user=firebase.auth().currentUser;
    let files=Array.from(filez);
    files.forEach(function(file) {
        let storageRef=firebase.storage().ref('data/'+user.uid+'/posts/'+file.name);
            storageRef.put(file).then(function(snapshot){
                snapshot.ref.getDownloadURL().then(function(url) {
                    urlarray.push(url);
                })
            })
        });
    if (!urlarray){
        reject("oops");
    }
    else {
        resolve(urlarray);
    }
});
}

Here is the part of the upload function code that would upload all the relevant data to the database, including the array of URLs returned by the promise in the function above. (I omitted the rest of the code to make the case concise)

        let userpostRef=firebase.database().ref('posts/');
        let newpostRef=userpostRef.push();
                    newpostRef.set({
                        userid: user.uid,
                        post_target: rtarget,
                        post_content:rtext,
                        time: firebase.database.ServerValue.TIMESTAMP
                    }).then(function(){
                            url_array_get().then(function(result){
                            newpostRef.once('value', function(snapshot) {
                                newpostRef.update({
                                    postnum:snapshot.key,
                                    photolink:result
                                })
                        })})}).
            then(function(){
                alert("Upload successful!");
                window.location.href='/'+username;
            })
            .catch(function(error){
                alert("Error!");    
            });               
        }

Here is the issue: The code would write to database everything except the 'photolink', which should be the array of URLs.
Here is what I found out doing debugging:
-Photos are stored without any problem.
-urls are downloaded for each file, and urlarray is returned successfully in the execution code as expected.
What might have gone wrong? I am lost here. Any advice would be very much welcome. Thanks a lot!


Solution

  • Each time you call storageRef.put(...) it starts an asynchronous operation. Right now your `` doesn't wait for these asynchronous operations to complete, and instead returns the list of URLS before it's been populated.

    The easiest way to see this is by adding some simple logging to your code:

    function url_array_get(){
      return new Promise(function(resolve,reject){
        let filez=review_photo.files;
        let urlarray=[];
        let user=firebase.auth().currentUser;
        let files=Array.from(filez);
        files.forEach(function(file) {
            console.log("Starting to put file...");
            let storageRef=firebase.storage().ref('data/'+user.uid+'/posts/'+file.name);
                storageRef.put(file).then(function(snapshot){
                    console.log("Upload done, getting download URL...");
                    snapshot.ref.getDownloadURL().then(function(url) {
                        console.log("Download URL gotten, adding to array...");
                        urlarray.push(url);
                    })
                })
            });
        if (!urlarray){
            reject("oops");
        }
        else {
            console.log("Resolving with "+urlarray.length+" download URLs");
            resolve(urlarray);
        }
      });
    }
    

    When you run this code, the output will look like:

    Starting to put file...

    Starting to put file...

    Starting to put file...

    Resolving with 0 download URLs

    Upload done, getting download URL...

    Download URL gotten, adding to array...

    Upload done, getting download URL...

    Download URL gotten, adding to array...

    Upload done, getting download URL...

    Download URL gotten, adding to array...

    That is not the order you want of course, as you're returning the array before you added any download URL to it, and even before any of the uploads complete.


    The solution is (as always when it comes to asynchronous operations) to wait until all operations have finished before resolving/returning. You can most easily do this with Promise.all() with something like this:

    function url_array_get(){
        let promises = [];
        let filez=review_photo.files;
        let user=firebase.auth().currentUser;
        let files=Array.from(filez);
        files.forEach(function(file) {
            let storageRef=firebase.storage().ref('data/'+user.uid+'/posts/'+file.name);
            promises.push(
                storageRef.put(file).then(function(snapshot){
                    return snapshot.ref.getDownloadURL()
                })
            });
        });
        return Promise.all(promises);
    }
    

    Or slightly shorter:

    function url_array_get(){
        let user=firebase.auth().currentUser;
        let ref = firebase.storage().ref('data/'+user.uid+'/posts/');
        let files=Array.from(review_photo.files);
        let promises = files.map(function(file) {
            return ref.child(file.name).put(file).then(function(snapshot){
                return snapshot.ref.getDownloadURL()
            })
        });
        return Promise.all(promises);
    }