【问题标题】:Synchronos file read in Javascript用 Javascript 读取 Synchronos 文件
【发布时间】:2018-03-02 14:13:34
【问题描述】:

我有以下文件选择器对话框。我想允许选择多个文件。选择文件后,我想呈现所选文件的预览。我的代码是:

<input
          type="file"
          multiple
          accept=".png, .jpeg"

          onChange={e => {
            this.setState({ previews: [] });

            if (!e.target.files) {
              return;
            }

            var previews = [];

            for (var i = 0; i < e.target.files.length; i++) {
              var file = e.target.files[i];
              (function(file, previews) {
                var reader = new FileReader();

                reader.onload = function(e) {
                  // console.log(e.target.result);
                  console.log("Just pushed a new preview");
                  previews.push(e.target.result);
                };
                reader.readAsDataURL(file);
              })(file, previews);
            }

            console.log("All files read");
            // I want this to be called only after
            // all the file contents are read
            this.setState({ previews });
          }}
        />

但是,使用上面的代码,我的Just pushed a new preview 是在控制台上打印All files read 之后打印的。

有没有办法让我完成 FileReader 循环(关闭)然后调用 this.setState({previews})

如果重要的话,所有这些代码都在一个反应​​文件中。

【问题讨论】:

  • 不阻塞主线程是异步的。这可能会导致页面不响应,具体取决于文件大小。所以它不允许在页面中。但是 (FileReaderSync)[developer.mozilla.org/en-US/docs/Web/API/FileReaderSync] 存在,可在 workers 中访问。
  • 您可以在onLoad 回调和set previews 中添加检查,如果所有文件都已读取。
  • 如何在onLoad 回调中知道是否已读取所有文件。我无法将ie.target.files.length 传递给onLoad 回调。即使我找到了传递索引和总长度的方法,我也无法可靠地知道所有文件都已读取。第 5 个文件读取可能会在第 3 个文件读取之前完成,等等。或者还有其他方法吗?
  • 你不必通过它。您可以在 function 内访问 ie.target,因为它们在父范围内。
  • 是的,我不必通过它们。我同意。但是如果第 5 次文件读取在第 3 次文件读取之前完成,我将无法知道所有文件读取都已完成(除非我维护某种可能导致重叠写入的 totalFilesRead 变量)。

标签: javascript reactjs asynchronous closures


【解决方案1】:

使用 Promise.all 等待所有的承诺都完成,以了解所有文件是否都已处理。而且,您需要在读取完文件后立即更新状态。最好添加一个进度条和 setState 来更新它。

var Sample = React.createClass({
  getInitialState: function () { return { previews: [], allfilesdone:false }; }, 
  render: function() {
    var p = this.state.previews.map((p) => <div>{p}</div>);
    return (
      <div style = {this.styler}>
        <input id="upload" multiple
          accept=".txt" type="file"
           onClick={(e) => this.setState({allfilesdone: false})}
           onChange={(event)=> { 
                var s = this;
                this.setState({previews:[], allfilesdone: false});
                var fs = event.target.files;
                Promise.all([...fs].map((file) => {
                  var reader = new FileReader();
                  return new Promise((resolve,reject) => {
                    reader.onload = (ev) => {
                        resolve(ev.target.result);
                        var pt = this.state.previews;
                        pt.push(ev.target.result);
                        s.setState({previews: pt});
                    }
                    reader.readAsText(file);
                  });
                }))
                  .then(ft => {
                        this.setState({allfilesdone: true});
                      });
            }}/>
        <div>all processed: {this.state.allfilesdone?"true":"false"}</div>
        <div>number of files: {this.state.previews.length}</div>
        <div>{p}</div>
      </div>
    );
  }
});

React.render(
  <Sample />, 
  document.getElementById('mount-point'));

【讨论】:

  • 我使用文本文件来展示这个概念。您可以将其更改为图像。用你想要的修改 readAsText。
  • 谢谢。这就是我试图做的。但我无法兑现承诺,因为我错过了 resolve,reject 电话。谢谢。这是一种线程安全的方法,而不是公认的答案(仅当 JS 在浏览器中是单线程时才有效)。
【解决方案2】:

您需要在 onLoad 事件中移动 setState。由于文件读取器是异步的,因此在 onLoad 之外设置状态将在事件完成之前触发它,并会导致 previews 数组为空或不完整。

 var previews = [];
 var s = this;

 for (var i = 0; i < e.target.files.length; i++) {
   var file = e.target.files[i];
      var reader = new FileReader();

      reader.onload = function(e) {
        previews.push(e.target.result);
        s.setState({ previews });
      };

      reader.readAsDataURL(file);
 }

【讨论】:

  • 这不是线程不安全的吗?两个不同的线程可以并行将预览设置为错误的值?
  • @SankarP 不,这不会是因为方法内的本地预览数组被追加,并且它的副本保持在状态。此外,在使用 js 时,线程处理也不再那么重要了。
  • 我已经接受了答案,因为它解决了问题。谢谢。但是,我相信正确的答案是使用(类似于 promises)来确保所有 previews.push 都已完成,然后仅调用一次 setState 以在状态中设置正确的 previews 数组。我们也可以使用 WaitGroups 之类的东西。我不是在批评你的回答,只是觉得必须有一个更纯粹的解决方案。
  • 另外,您的代码需要进行编辑。 this.setState 没用。所以我们需要在进入循环之前分配var s = this(在初始化previews之后)然后调用s.setState。如果您可以编辑答案,那就太好了。非常感谢您的代码:-)
猜你喜欢
  • 1970-01-01
  • 2018-03-03
  • 2023-03-24
  • 1970-01-01
  • 1970-01-01
  • 2015-06-12
  • 2014-10-16
  • 2012-08-17
  • 1970-01-01
相关资源
最近更新 更多