【问题标题】:How to async all uploaded files before last step?如何在最后一步之前异步所有上传的文件?
【发布时间】:2021-05-02 12:06:45
【问题描述】:

我希望用户上传多个图像文件,应用程序读取所有图像文件并推送到一个数组中,当全部完成然后lastStep()

<input type="file" (change) = "fileChangedEvent($event);" multiple/>

fileChangedEvent(event: any): void
{
    for (let f of event.target.files)
    {
        var reader = new FileReader();
        reader.readAsDataURL(f);
        reader.onload= (event) => {this.arrayThumbnail.push(event.target.result);}; 
    } 
    // when all done
    lastStep();  // work on arrayThumbnail[]
}

但是lastStep() 总是在for ... of 循环之前运行,导致它在一个空的arrayThumbnail[] 上运行。关注async/await 会产生相同的结果:

fileChangedEvent(event: any): void
{
    async() => {
    for (let f of event.target.files)
        {
            var reader = new FileReader();
            await reader.readAsDataURL(f);
            await reader.onload= (event) => {this.arrayThumbnail.push(event.target.result);}; 
        } 
        // when all done
        lastStep();  // work on arrayThumbnail[]
    }
}
    

显式Promise 也不起作用:

myPromise(fs: any): Promise<number>
{
    var bogus = new Promise<number>((resolve, reject) =>
    {
        for (let f of fs)
        {
            var reader = new FileReader();
            reader.readAsDataURL(f);
            reader.onload= (event) => {this.arrayThumbnail.push(event.target.result);};    
        };
        resolve(1);
    })
    return bogus;
}

fileChangedEvent(event: any): void
{
    this.myPromise(event.target.files).then(
    x=>
    {
        lastStep();
    });
}

【问题讨论】:

    标签: typescript async-await promise


    【解决方案1】:

    你有正确的想法来使用 Promise,你只需要稍微不同地把事情联系起来。运行下面的sn-p,选择两个小文件测试一下!

    // onChange listener
    function onChange(event) {
      const promises = []
      for (const file of event.target.files)
        promises.push(readFile(file))
      Promise.all(promises).then(lastStep)
    }
    
    // read one file
    function readFile(f) {
      return new Promise((resolve, reject) => {
        const reader = new FileReader()
        reader.readAsDataURL(f)
        reader.onload = event => resolve(event.target.result)
        reader.onerror = reject
      })
    }
    
    // example last step
    function lastStep(data) {
      console.log("last step")
      console.log(`uploaded ${data.length} files`)
      for (const blob of data) {
        const pre = document.createElement("pre")
        pre.textContent = blob
        document.body.appendChild(pre)
      }
    }
    
    document.forms.myapp.files.addEventListener("change", onChange)
    <form id="myapp">
      <input type="file" name="files" multiple>
    </form>

    一旦你明白了这一点,就知道我们可以更轻松地重写onChange -

    // onChange listener simplified
    function onChange(event) {
      Promise.all(Array.from(event.target.files, readFile)).then(lastStep)
    }
    

    现在真的没有理由将onChangelastStep 分开。使用asyncawait 我们可以很容易地将两者合二为一-

    // onChange listener
    async function onChange(event) {
      const data = await Promise.all(Array.from(event.target.files, readFile))
      console.log(`uploaded ${data.length} files`)
      for (const blob of data) {
        const pre = document.createElement("pre")
        pre.textContent = blob
        document.body.appendChild(pre)
      }
    }
    
    // read one file
    function readFile(f) {
      return new Promise((resolve, reject) => {
        const reader = new FileReader()
        reader.readAsDataURL(f)
        reader.onload = event => resolve(event.target.result)
        reader.onerror = reject
      })
    }
    
    document.forms.myapp.files.addEventListener("change", onChange)
    <form id="myapp">
      <input type="file" name="files" multiple>
    </form>

    没有承诺

    “第二个想法,我们在这里做的是每个文件都有一个 Promise,然后等到全部解决。是否可以为所有文件做一个 Promise?”

    Promises 的好处是它们是轻量级且可组合的,这意味着您可以从较小的异步计算中构建更大的异步计算。编写回调更具挑战性,因此更容易犯常见错误。如果我们愿意,我们可以将所有这些包装在一个 Promise 中,或者完全跳过 Promise -

    // onChange listener
    function onChange(event) {
      const data = []
      const noEror = true
    
      // for each file, f
      for (const f of event.target.files) {
    
        // readFile with callback
        readFile(f, (err, result) => {
    
          // handle errors
          // only call error handler a maximum of one time
          if (err && noError) {
            noError = false
            return handleError(err)
          }
          // add individual result to data
          data.push(result) 
          
          // once data.length is equal to number of input files
          // then proceed to the last step
          if (data.length == event.target.files.length)
            return lastStep(data)
        })
      }
    }
    
    // read one file
    function readFile(f, callback) {
      const reader = new FileReader()
      reader.readAsDataURL(f)
      reader.onload = event => callback(null, event.target.result)
      reader.onerror = err => callback(err)
    }
    
    function handleError (err) { ... }
    function lastStep (data) { ... }
    

    如您所见,对于一个应该是简单的程序,要写的内容很多。每次我们需要类似的功能时,所有这些工作都会重复。为了解决这个问题,我们可以编写一个通用的asyncEach 实用程序,当我们需要使用异步操作f 迭代数组时可以使用它,并指定一个最终的callback 用于所有操作完成时-

    // asyncEach generic utility
    function asyncEach(arr, f, callback, data = []) {
      if (arr.length == 0)
        callback(null, data)
      else
        f(arr[0], (err, result) =>
          err
            ? callback(err)
            : asyncEach(arr.slice(1), f, callback, [...data, result]) 
        )
    }
    
    // read one file
    function readFile(f, callback) {
      const reader = new FileReader()
      reader.readAsDataURL(f)
      reader.onload = event => callback(null, event.target.result)
      reader.onerror = err => callback(err)
    }
    
    // onChange listener
    function onChange(event) {
      asyncEach(Array.from(event.target.files), readFile, (err, data) => {
        if (err) return console.error(err)
        console.log(`uploaded ${data.length} files`)
        for (const blob of data) {
          const pre = document.createElement("pre")
          pre.textContent = blob
          document.body.appendChild(pre)
        }
      })
    }
    
    document.forms.myapp.files.addEventListener("change", onChange)
    <form id="myapp">
      <input type="file" name="files" multiple>
    </form>

    我认为这是一个很棒的练习,但大多数人不会尝试编写自己的asyncEach,而对这类东西的共同需求是async 等流行库的基础。然而,由于 Promise 和新的 async-await 语法的广泛成功,这些旧的和繁琐的回调模式已经很少需要了。

    【讨论】:

    • 哇,很棒的例子和简单易懂的语言!
    • 第二个想法,我们在这里做的是每个文件都有一个Promise,然后等到一切都解决了。是否可以对所有文件做 一个 承诺?
    • 你可以,但不值得增加机制来协调所有单独的 onload 回调。 Promises 的好处是它们是轻量级且可组合的,这意味着您可以从较小的异步计算中构建更大的异步计算。编写回调更具挑战性,因此更容易犯常见错误。我可以包含一个使用单个 Promise 的演示,以便您可以看到它。我会尽快处理的:D
    • @Jeb50 我更新了我的答案来解决这个问题。我希望证明使用 Promise 以外的其他东西比它的价值更麻烦。如果您有任何后续问题,我很乐意为您提供帮助!
    • 这是一个很棒的插图和解释。谢谢!
    猜你喜欢
    • 2017-11-25
    • 2023-03-05
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2013-07-30
    • 1970-01-01
    相关资源
    最近更新 更多