如何在最后一步之前异步所有上传的文件? [英] How to async all uploaded files before last step?

查看:37
本文介绍了如何在最后一步之前异步所有上传的文件?的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

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

I want users upload multiple image files, app reads all of them and push into an array, when all finish then lastStep()

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 产生相同的结果:

But lastStep() always run before for ... of loop, resulting it works on an empty arrayThumbnail[]. Following async/await produces the same result:

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

推荐答案

你有使用 Promise 的正确想法,你只需要稍微改变一些事情.运行下面的代码片段并选择两个小文件进行测试!

You have the right idea to use promises, you just need to hook things up a little differently. Run the snippet below and select two small files to test it out!

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

Once you've wrapped your head around that, know that we can rewrite onChange even easier -

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

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

And now there's really no reason to separate onChange and lastStep. Using async and await we can very easily combine the two into one -

// 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,然后等待一切解决.是否可以对所有文件做一个承诺?"

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

The benefit of Promises is they are light-weight and composable, meaning you can build larger async computations out of smaller ones. Composing callbacks is much more challenging and therefore easier to make common mistakes. We could wrap all of this in a single promise if we wanted to, or skip promises altogether -

// 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 迭代数组,就可以使用它,并指定一个最终的 回调用于所有操作完成时-

As you can see, that's a lot to write for what should be a simple procedure. And all of this work would be duplicated each time we needed similar functionality. To get around this, we could write a generic asyncEach utility that can be used whenever we need to iterate over an array using an asynchronous operation f, and specify a final callback for when all operations are complete -

// 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,而对此类事物的共同需求是诸如 异步.然而,由于 Promise 和新的 async-await 语法的广泛成功,几乎不需要这些旧的和繁琐的回调模式.

I think this is a wonderful exercise but most people wouldn't attempt writing their own asyncEach and the common need for this type of thing was the basis of popular libraries like async. However because of the widespread success of Promise and new async-await syntax, there is little need for these old and cumbersome callback patterns.

这篇关于如何在最后一步之前异步所有上传的文件?的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

查看全文
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆