首页 > 解决方案 > 异步函数的问题,JS。我可以使用 Promise 吗?

问题描述

我有一些我认为现在不能正常工作的代码,因为我添加了一些东西来使用一些 JS 获取 MIME 类型(真正的 MIME 类型)。它调用 checkDicomMime(file),它异步读取要上传的文件,并确定 MIME 类型是否与我正在寻找的匹配。

我认为 MIME 类型检测正在工作,但因为读取文件需要时间,我认为其余代码在读取 MIME 类型之前执行。

以前,我只是检查文件扩展名,这是同步完成的,因此函数中的“reader.onload = function (evt) {”块中的变量是内联设置的。现在,它调用该函数,该函数正确检测到 MIME 类型,但看起来调用函数已完成,其余代码在 MIME 类型检测完成之前执行,因此它为之前列表中的每个文件发布表单MIME TYPE 检测完成。total = counts.process 现在为零,而不是要处理的文件总数,因此 counts 和 files.process 和 badfiles 要么没有改变,要么只有在所有文件都发布后才会改变。我检查了一些调试,看起来它们是在文件发送后设置的。还,其他 SO 帖子谈到仅读取必要数量的字节以检测 MIME 类型,而不是读取整个文件。不确定该怎么做。

我在这里得到了 DICOM 检查功能: 检查 Dicom

这里有一些关于使用 JS 进行 MIME 类型检测的讨论:

上传前如何使用 javascript 检查文件 MIME 类型?

相关代码为:

var counts;

// Detects when a Folder is selected, Folder, not a file.

picker.addEventListener('change', e => {

    counts = {process:0,omit:0};
    requestcounter = 0;
    responsecounter = 0;
    total = 0;
    skipotherrequests = 0;
    parsedepoch = new Date().toISOString().match(/(\d{4}\-\d{2}\-\d{2})T(\d{2}:\d{2}:\d{2})/);
    datetimestamp = parsedepoch[1] + "-" + parsedepoch[2].replace(/:/g, "-");
    //alert(datetimestamp);
    picker.setAttribute('data-timestamp', datetimestamp);

    // preprocess checking

    var badfiles = [];

    var filelist = Array.from(picker.files);


    filelist.forEach(function(file, index) {
        // add it to the list, otherwise skip it
        checkDicomMime(file);  // calls the check for MIME type.
    });

    filelist.sort(function(a,b) {
    return a.name > b.name;
    });

    total = counts.process;  // omitting the ones that do not pass verification.

    badlist = "";

    badfiles.forEach( element => badlist += '<div>' + element + '</div>' );

    for (var i = 0; i < filelist.length; i++) {

    var file = filelist[i];
    if (file.process == 0) {
        let lineitem = statusitem(file, "Skipping file:  " + file.name);
        listing.insertAdjacentHTML('beforeend', lineitem);
    }
    else {
    sendFile(file);  // sends form and file
    }
    }
});

function checkDicomMime(file) {

        var reader = new FileReader();
        reader.readAsArrayBuffer(file);

        //Fired after sucessful file read, Please read documenation for FileReader
        reader.onload = function (evt) {
            if (evt.target.readyState === FileReader.DONE) {

                var array = new Uint8Array(evt.target.result);
                var s = "";
                var start = 128, end = 132;
                for (var i = start; i < end; ++i) {
                    s += String.fromCharCode(array[i]);
                }

                if (s == "DICM") {

                    alert("DICM a valid dicom file");

                    file.process = 1;
                    counts.process++;
                }

                else {

                    alert("DICM not found");
                     file.process = 0;
                     counts.omit++;
                     badfiles.push (file.name);
                }
            }
        }
}

然后 sendFile 函数的开头是:

sendFile = function(file) {

    if (skipotherrequests == 0) {

    var timestamp  = picker.dataset.timestamp;
    var formData = new FormData();
    // Set post variables 

    requestcounter = requestcounter + 1;
    formData.set('timestamp', timestamp); // One object file
    formData.set('counter', requestcounter);
    formData.set('total', total); 
    formData.set('type', type); 
    formData.set('webkitpath', file.webkitRelativePath); // One object file
    formData.set('file', file); // One object file
    //console.log(file);

    var request = new XMLHttpRequest();

    request.responseType = 'json';

    // HTTP onload handler

    request.onload = function() {

        if (request.readyState === request.DONE) {

标签: javascriptasynchronouspromisemultipartform-datafilereader

解决方案


现在,它调用该函数,该函数正确检测到 MIME 类型,但看起来调用函数已完成,其余代码在 MIME 类型检测完成之前执行,因此它为之前列表中的每个文件发布表单MIME TYPE 检测完成。

您可以更改checkDicomMime为 promise 并等待所有文件都被检查。

然后你可以继续循环处理它们并像你一样发送有效的。

当然,这需要一些代码重构。

例子

const picker = document.querySelector("#file");
const listing = document.querySelector("#listing");
const button = document.querySelector("#button");

picker.addEventListener('change', async event => {

  const counts = {
    process: 0,
    omit: 0
  };
  let requestcounter = 0;
  let responsecounter = 0;
  let total = 0;
  let skipotherrequests = 0;
  const [, datePart, timePart] = new Date().toISOString().match(/(\d{4}\-\d{2}\-\d{2})T(\d{2}:\d{2}:\d{2})/);
  const datetimestamp = `${datePart}-${timePart.replace(/:/g, "-")}`;
  picker.setAttribute('data-timestamp', datetimestamp);

  const files = Array.from(event.detail || event.target.files);
  const processList = await Promise.all(files.map(file => checkDicomMime(file)));

  processList.sort((prev, next) => {
    return prev.fileName > next.fileName;
  });

  const badlist = processList.filter(({
      isBadFile
    }) => isBadFile)
    .reduce((acc, result) => acc += `<div>${result.fileName}</div>`, '');

  const timestamp = picker.dataset.timestamp;
  for (let result of processList) {
    const file = result.file;
    const type = file.type;
  
    if (result.isBadFile) {
      let lineitem = statusitem(file, `Skipping file: ${result.fileName}`);
      listing.insertAdjacentHTML('beforeend', lineitem);
      continue;
    }
  
    console.log('sending file', file)
    requestcounter = requestcounter + 1;
    await sendFile(file, timestamp, requestcounter, total, type);
  }

});

function statusitem(file, text) {
  return `<div>${text}</div>`;
}

function checkDicomMime(file) {
  const fileReader = new FileReader();
  return new Promise((resolve, reject) => {

    fileReader.readAsArrayBuffer(file);
    fileReader.onload = function(event) {

      const target = event.target;
      const array = new Uint8Array(target.result);
      const start = 128
      const end = 132;
      const str = [...array.slice(128, 132)].map(value => String.fromCharCode(value)).join('');

      const result = {
        file,
        fileName: file.name,
        isBadFile: true
      }

      if (str == "DICM") {
        result.isBadFile = false;
      }

      fileReader.onload = null;
      resolve(result);
    }
  })
}

const sendFile = function(file, timestamp, requestcounter, total, type) {
  return new Promise((resolve, reject) => {
    const formData = new FormData();
    formData.set('timestamp', timestamp);
    formData.set('counter', requestcounter);
    formData.set('total', total);
    formData.set('type', type);
    formData.set('webkitpath', file.webkitRelativePath);
    formData.set('file', file);

    const request = new XMLHttpRequest();
    request.responseType = 'json';
    request.onload = function() {

      if (request.readyState === request.DONE) {
        resolve();
      }
    }
  })
}

function createInvalidFile() {
  const data = [new Uint8Array(Array(132).fill(0))]
  const file = new File(data, 'invalid-file.txt',{
  type: "text/plain"
  });
  return file;
}

function createValidFile() {
  const data = [new Uint8Array(Array(128).fill(0)), new Uint8Array([68, 73, 67, 77])]
  const file = new File(data, 'valid-file.txt', {
  type: "text/plain"
  });
  return file;
}

button.addEventListener("click", event => {
  const customEvent = new CustomEvent('change', {
    detail: [createInvalidFile(), createValidFile()]
  });
  picker.dispatchEvent(customEvent);
})
<input id="file" type="file" multiple>
<div id="listing"></div>
<button id="button">Send test files</button>


推荐阅读