HTML5的ondrop事件在zip.js完成操作之前返回

10
我的问题的关键是我需要异步使用datatransferitemlist,这与规范中描述的功能不一致,规范表示一旦事件结束,您将无法访问dataTransfer.items集合。

https://bugs.chromium.org/p/chromium/issues/detail?id=137231 http://www.whatwg.org/specs/web-apps/current-work/multipage/dnd.html#drag-data-store

罪犯案件如下。下面更详细地描述了我的问题和想法。
drophandler: function(event) {
    event.stopPropagation();
    event.preventDefault();
    event.dataTransfer.dropEffect = 'copy';
    zip.workerScriptsPath = "../bower_components/zip.js/WebContent/";
    zip.useWebWorkers = false; // Disabled because it just makes life more complicated
    // Check if files contains just a zip
    if (event.dataTransfer.files[0].name.match(/(?:\.([^.]+))?$/) == 'zip') {
        var reader = new FileReader();
        that = this;
        reader.onload = function(e) {
            that.fire('zipuploaded', e.target.result.split(',')[1]);
        }
        reader.readAsDataURL(event.dataTransfer.files[0]);
        // Rev up that in browser zipping
    } else {
        var that = this;
        var items = event.dataTransfer.items;
        // Async operation, execution falls through from here
        zip.createWriter(new zip.Data64URIWriter(), function(writer) {
            (function traverse(list, path, i, depth) {
                return new Promise(function(resolve, reject) {
                    var item;
                    if (depth == 0) {
                        if (i == list.length) {
                            writer.close(function(uri) {
                                that.fire('zipuploaded', uri.split(',')[1]); // Just the base64, please
                                fulfill(1);
                                return;
                            });
                        } else {
                            console.log(i);
                            console.log(list);
                            var item = list[i].webkitGetAsEntry();
                        }
                    } else {
                        if (i == list.length) {
                            resolve(0);
                            return;
                        } else {
                            item = list[i];
                        }
                    }
                    if (item.isFile) {
                        item.file(function(file) {
                            // Zipping operations done asynchronously, it'll fail by roughly the second operation
                            writer.add(path + file.name, zip.BlobReader(file), function() {
                                traverse(list, path, i + 1, depth).then(resolve(0)); // Next item
                            });
                        });
                    } else if (item.isDirectory) {
                        var dirReader = item.createDirReader();
                        dirReader.readEntries(function(entries) {
                            // Operate on child folder then the next item at this level
                            traverse(entries, path + item.name + "/", 0, depth + 1).then(function() {
                                traverse(list, path, i + 1, depth).then(resolve(0));
                            });
                        });
                    }
                });
            })(items, "", 0, 0); // Begin with datatransferitemlist, 0th element, depth 0
        });
        this.$.uploadarea.classList.remove('highlightdrag');
    }
    // When we exit it kills the event.dataTransfer.items
},

我正在使用异步的zip.js和HTML5 DnD API。在异步的zip.createWriter/writer.add操作完成之前,ondrop事件就已经结束了。我可以想到四种解决方法,但我不知道如何实现它们,希望得到一些建议。
  1. 阻塞直到createWriter完成。(阻塞javascript?不好)
  2. 防止ondrop锁定dataTransfer.items(这似乎是为了安全起见,所以不太可能)
  3. 先同步复制dataTransfer.items的内容(可能非常慢)
  4. 全部同步进行(我认为zip.js不允许这样做,JsZip允许,但由于其在处理大文件集方面有自己的限制,我放弃了它)

一个问题是你在 traverse(list, path, i + 1, depth).then(resolve(0)) 立即调用了 resolve 函数,而不是引用它。不确定预期结果是什么?将所有丢失的文件添加到单个 .zip 文件夹中吗? - guest271314
1个回答

4

HTML5的拖放功能正常工作。问题在于,如果您在前一个文件完成之前添加一个文件,zip.js 将会静默地中断。可以通过连续调用 writer.add 方法来解决此问题。

此代码片段可能无法运行,请参考此处的示例。

此示例将拖放文件的结构平铺,并在连续的过程中将其添加到 zip 文件中。

function mes(it) {
  const m = document.querySelector('#mes')
  return m.textContent = it + '\n' + m.textContent
}

function read(items) {
  return Promise.all(items.map(item => {
    if (item.isFile) return [item]
    return new Promise(resolve => item.createReader().readEntries(resolve))
    .then(entries => {
      entries.forEach(it => it.path = item.path + '/' + it.name)
      return read(entries)
    })
  })).then(entries => entries.reduce((a, b) => a.concat(b)))
}

function handleResult(blob){
  const res = document.querySelector('#result')
  res.download = 'files.zip'
  res.href = window.URL.createObjectURL(blob)
  res.textContent = 'download zipped file'
}

function handleItems(items){
  mes(items.length)
  items.forEach(item => item.path = item.name)
  const initZip = new Promise(resolve =>
    zip.createWriter(new zip.BlobWriter, resolve)
  )
  const getFiles = read(items).then(entries => {
    return Promise.all(entries.map(entry =>
      new Promise(resolve =>
        entry.file(file => {
          file.path = entry.path
          resolve(file)
        })
      )
    ))
  })
  return Promise.all([getFiles, initZip]).then(([files, writer]) =>
    files.reduce((current, next) =>
      current.then(() =>
        new Promise(resolve => {
          mes(next.path)
          writer.add(next.path, new zip.BlobReader(next), resolve)
        })
      )
    , Promise.resolve())
    .then(() => writer.close(handleResult))
  )
}

zip.useWebWorkers = false
const drop = document.querySelector('#drop');

['dragover', 'drop'].forEach(name =>
  drop.addEventListener(name, ev => ev.preventDefault())
)
drop.addEventListener('drop', ev => {
  const items = [].slice.call(ev.dataTransfer.items)
  .map(item => item.webkitGetAsEntry())
  return handleItems(items)
})
html, body, #drop {
  height: 100%;
  width: 100%;
}
<script src="http://gildas-lormeau.github.io/zip.js/demos/zip.js"></script>
<script src="http://gildas-lormeau.github.io/zip.js/demos/deflate.js"></script>


<div id="drop">
  Drop here!
  <br>
  <a id="result"></a>
</div>
<pre id="mes"></pre>

jszip比这个简单得多,你可能想试试它。


当添加多个文件时,如果在前一个文件完成之前添加了一个文件,zip.js会默默地中断。这可以通过连续调用writer.add来解决。 - moggers
当添加多个文件时,如果在上一个文件完成之前添加文件,则zip.js会默默地中断。这可以通过连续调用writer.add来修复。 这就是我尝试的方法,将traverse()放在writer.add的成功回调函数中。我以前使用过JsZip。但它会导致内存使用量膨胀到2GB,并且我发现了一堆github问题,描述它不适用于大型文件集。而zip.js具有Web Workers,因此似乎在各方面都更好。我会玩弄你的代码片段一会儿。 - moggers

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接