Javascript DataTransfer 项目不会通过异步调用持久化

我正在使用 Vuejs 和 DataTransfer 异步上传文件,我希望允许一次拖放多个文件进行上传.

I am using Vuejs along with DataTransfer to upload files asynchronously, and I want to allow multiple files to be dragged and dropped for upload at once.

我可以进行第一次上传,但在上传完成时,Javascript 已经收集垃圾或更改了 DataTransfer 项目对象.

I can get the first upload to happen, but by the time that upload is done, Javascript has either garbage collected or changed the DataTransfer items object.

我怎样才能重做这个(或克隆事件/DataTransfer 对象),以便在整个 ajax 调用过程中我仍然可以使用数据?

How can I rework this (or clone the event/DataTransfer object) so that the data is still available to me throughout the ajax calls?

我已按照 MDN 文档了解如何使用 DataTransfer,但我很难将它应用到我的具体案例中.我也尝试过复制事件对象,正如您在我的代码中看到的那样,但它显然不会进行深度复制,只是传递引用,这没有帮助.

I've followed the MDN docs on how to use DataTransfer but I'm having a hard time applying it to my specific case. I also have tried copying the event objects, as you can see in my code, but it obviously does not do a deep copy, just passes the reference, which doesn't help.

    methods: {
        dropHandler: function (event) {
            if (event.dataTransfer.items) {
                let i = 0;
                let self = this;
                let ev = event;

                function uploadHandler() {
                    let items = ev.dataTransfer.items;
                    let len = items.length;

                    // len NOW EQUALS 4

                    console.log("LEN: ", len);
                    if (items[i].kind === 'file') {
                        var file = items[i].getAsFile();
                        $('#id_file_name').val(file.name);
                        var file_form = $('#fileform2').get(0);
                        var form_data = new FormData(file_form); 

                        if (form_data) {
                            form_data.append('file', file);
                            form_data.append('type', self.type);
                        }

                        $('#file_progress_' + self.type).show();
                        var post_url = '/blah/blah/add/' + self.object_id + '/'; 
                        $.ajax({
                            url: post_url,
                            type: 'POST',
                            data: form_data,
                            contentType: false,
                            processData: false,
                            xhr: function () {
                                var xhr = $.ajaxSettings.xhr();
                                if (xhr.upload) {
                                    xhr.upload.addEventListener('progress', function (event) {
                                        var percent = 0;
                                        var position = event.loaded || event.position;
                                        var total = event.total;
                                        if (event.lengthComputable) {
                                            percent = Math.ceil(position / total * 100);
                                            $('#file_progress_' + self.type).val(percent);
                                        }
                                    }, true);
                                }
                                return xhr;
                            }
                        }).done((response) => {
                                i++;
                                if (i < len) {

                                    // BY NOW, LEN = 0.  ????

                                    uploadHandler();
                                } else {
                                    self.populate_file_lists();
                                }
                            }
                        );
                    }
                }

                uploadHandler();
            }
        },

推荐答案

一旦你调用了await,你就不再在函数的原始调用堆栈中了.这在事件侦听器中尤其重要.

Once you call await you're no longer in the original call stack of the function. This is something that would matter particularly in the event listener.

我们可以用setTimeout重现同样的效果:

We can reproduce the same effect with setTimeout:

dropZone.addEventListener('drop', async (e) => {
  e.preventDefault();
  console.log(e.dataTransfer.items);
  setTimeout(()=> {
    console.log(e.dataTransfer.items);
  })
});

例如拖动四个文件会输出:

For example, dragging four files will output:

DataTransferItemList {0: DataTransferItem, 1: DataTransferItem, 2: DataTransferItem, 3: DataTransferItem, length: 4}  
DataTransferItemList {length: 0}

事件发生后,状态发生了变化,物品丢失了.

After the event had happened the state has changed and items have been lost.

有两种方法可以处理这个问题:

There are two ways to handle this issue:

  • 复制项目并对其进行迭代
  • 将异步作业(Promises)推送到数组中,稍后使用 Promise.all
  • 处理它们

第二种解决方案比在循环中使用 await 更直观.另外,请考虑并行连接受到限制.使用数组,您可以创建块来限制同时上传.

The second solution is more intuitive than using await in the loop. Also, consider parallel connections are limited. With an array you can create chunks to limit simultaneous uploads.

function pointlessDelay() {
  return new Promise((resolve, reject) => {
    setTimeout(resolve, 1000);
  });
}

const dropZone = document.querySelector('.dropZone');

dropZone.addEventListener('dragover', (e) => {
  e.preventDefault();
});

dropZone.addEventListener('drop', async (e) => {
  e.preventDefault();
  console.log(e.dataTransfer.items);
  const queue = [];
  
  for (const item of e.dataTransfer.items) {
    console.log('next loop');
    const entry = item.webkitGetAsEntry();
    console.log({item, entry});
    queue.push(pointlessDelay().then(x=> console.log(`${entry.name} uploaded`)));
  }
  
  await Promise.all(queue);
});

body {
  font-family: sans-serif;
}

.dropZone {
  display: inline-flex;
  background: #3498db;
  color: #ecf0f1;
  border: 0.3em dashed #ecf0f1;
  border-radius: 0.3em;
  padding: 5em;
  font-size: 1.2em;
}

<div class="dropZone">
  Drop Zone
</div>

相关文章