如何将多个文件上传到 Firebase?

Posted

技术标签:

【中文标题】如何将多个文件上传到 Firebase?【英文标题】:How to upload multiple files to Firebase? 【发布时间】:2017-05-31 02:54:13 【问题描述】:

有没有办法将多个文件上传到 Firebase 存储。它可以在一次尝试中上传单个文件,如下所示。

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;

    
  );

);

如何将多个文件上传到 Firebase 存储。

【问题讨论】:

当您选择多个文件时,我猜e.target.files 包含多个条目?如果是这样,e.target.files.forEach(function(file) /* Do what you did before to upload each file */ ); 如果您想上传文件夹中的所有文件(嵌套或其他),解决方案在这里 - ***.com/a/69669345/1205871 【参考方案1】:

上传文件并获取下载网址

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

上传多个文件并获取下载地址

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

【讨论】:

【参考方案2】:

使用 rxjs 的 switchMap 和 combineLatest 实现 Angular 的火力非常轻松

【讨论】:

【参考方案3】:

@isuru,上传问题的人在下面提供了一个很好的解决方案。但是,一些 firebase 功能已经更新。因此,我刚刚使用 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)
    )
  );

【讨论】:

【参考方案4】:

所有的承诺很快就会变得混乱,为什么不使用 asyncawait 来代替?

在这里,我有一个功能可以跟踪从要上传的输入/文件控件中选择的所有图像:

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

我还有另一个功能,该功能将由用户在准备进行实际上传时单击的按钮触发:

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

我们基本上循环遍历每个图像并执行上传,并将图像路径一一推送到一个单独的 imagePaths 数组中,因为每个路径都以自己的速度完成,然后我在知道它们全部完成后抓取所有路径通过比较图像的长度和它们的最终路径来完成。

【讨论】:

【参考方案5】:

这是对标记答案的修改,适用于希望等待每次上传完成后再开始其他上传的人。

正如标记的答案所代表的那样,承诺没有解决或拒绝,所以当从循环开始上传时,一切都刚刚开始,第一个文件,第二个.....

想想每 20mb 上传 3 次。循环将几乎同时调用上传函数,使它们几乎同时运行。

这个答案解决了这个问题,使用async/await 处理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);
            
        );
    );

【讨论】:

【参考方案6】:

我们可以像这样组合多个 Promise

Promise.all([promise1, promise2, promise3]).then(function(values) 
  console.log(values);
);

我们可以像这样链接 Promise

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!!!");
);

想法是将上传文件承诺与 Promise.all 结合起来,并将它们链接在一起以在每次上传后获取下载 URL

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

【讨论】:

如何跟踪每个人的进度以更新 UI【参考方案7】:
        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)
        );

【讨论】:

【参考方案8】:

Firebase 存储使用 Promise,因此您可以使用 Promise 来实现它。

以下是涵盖此主题的 firebase 博客文章: Keeping our Promises (and Callbacks)


给 Promise.all() 一个“Promise 数组”

Promise.all(
  // Array of "Promises"
  myItems.map(item => putStorageItem(item))
)
.then((url) => 
  console.log(`All success`)
)
.catch((error) => 
  console.log(`Some failed: `, error.message)
);

上传每个文件并返回一个 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)
  );

YourPathYourFile 可以与 myItems 数组一起携带(因此是 item 对象)。

为了可读性,我在这里省略了它们,但你明白了这个概念。

【讨论】:

您能否将其更新为当前的 Firebase 文档,或者至少分享如何获取当前正在运行的任务的进度,以便相应地更新 UI【参考方案9】:

我相信有一个更简单的解决方案:

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

【讨论】:

尚未测试但似乎很优雅的方法,有没有办法我们可以跟踪上传进度?而不是使用then/catch,我们可以使用on吗?【参考方案10】:

我找到了上述问题的解决方案,我喜欢把它放在这里,因为它对任何人都有用。

//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 你能分享你的html源文件吗?你在预览图片吗? @ArunaRajput 我使用上述方法将多个图像上传到 Firebase 存储。其实你想达到什么目的? 非常感谢!我花了一整天的时间试图用 File 对象来理解整个事情,你的回答很有帮助。我唯一能想到在这里补充的是整体的上传进度,在这里实现起来并不容易。 @user1040495 这不是真的,他们是并行上传的

以上是关于如何将多个文件上传到 Firebase?的主要内容,如果未能解决你的问题,请参考以下文章

如何将多个(不同)文件上传到 Cloud Storage Firebase?

将多个图像上传到 Firebase

从多个文件上传 Firebase 存储中获取下载 url

将文件上传到 Firebase 后如何获取文件 URL?

将多个图像的异步图像上传到firebase存储

Flutter:如何将pdf文件上传到firebase存储?