异步功能问题,JS。我可以使用Promises吗?

问题描述 投票:1回答:2

我有一些我认为现在无法正常运行的代码,因为我添加了一些内容以使用某些JS获得MIME类型(真正的MIME类型)。它调用checkDicomMime(file),并异步读取要上传的文件,并确定MIME类型是否与我要的内容匹配。

我认为MIME类型检测有效,但是由于读取文件花费时间,我认为其余代码在完成读取MIME类型之前就已执行。

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

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

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

How to check file MIME type with javascript before upload?

相关代码是:

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) {
javascript asynchronous promise multipartform-data filereader
2个回答
1
投票

现在,它将调用该函数,并且该函数正确检测到MIME类型,但看起来调用函数已完成并且其余代码在MIME TYPE检测完成之前执行,因此它为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>

0
投票

已经接受了一个答案,但是发布了修改后的代码,现在看来效果很好。如果您实际上有一个包含某些.dcm文件的文件夹(带有或不带有文件扩展名),则该文件夹应排除不是真实.dcm文件的任何内容,并且可以将其扩展为其他类型的文件,如另一篇文章中所述我引用了。

还有一个库将为您执行此操作,尽管不能确定它只是读取检测MIME类型所需的前几个字节:

GitHub Library to Detect MIME Client Side

此外,如果您运行代码段,它将触发一系列带有FORM数据集的AJAX请求,例如:

-----------------------------391231719611056787701959262038
Content-Disposition: form-data; name="method"
UploadFolder
-----------------------------391231719611056787701959262038
Content-Disposition: form-data; name="timestamp"
2020-05-21-02-21-45
-----------------------------391231719611056787701959262038
Content-Disposition: form-data; name="counter"
3
-----------------------------391231719611056787701959262038
Content-Disposition: form-data; name="total"
14
-----------------------------391231719611056787701959262038
Content-Disposition: form-data; name="anon_normal"
<?php echo $_GET['anon_normal'] ?>
-----------------------------391231719611056787701959262038
Content-Disposition: form-data; name="userid"
<?php echo $_GET['userid'] ?>
-----------------------------391231719611056787701959262038
Content-Disposition: form-data; name="type"
-----------------------------391231719611056787701959262038
Content-Disposition: form-data; name="webkitpath"
dicomtest/28896580
-----------------------------391231719611056787701959262038
Content-Disposition: form-data; name="file"; filename="dicomtest/28896580"

。 。 。 。

进度计数器和其他功能在这里无法使用,因为服务器没有响应。 PHP脚本通常会返回JSON:

file    Object { name: "28896579.dcm", size: 547440, type: "application/dicom", … }
name    "28896579.dcm"
size    547440
type    "application/dicom"
ext "dcm"
status  "Uploaded"
counter "2"

直到“最后一个”文件被处理,尽管这并不总是最后一个响应,并且返回类似:

file    Object { name: "28896590.dcm", size: 547436, type: "application/dicom", … }
name    "28896590.dcm"
size    547436
type    "application/dicom"
ext "dcm"
status  "Done"
results "bunch of HTML"

您确实需要一些.dcm文件,无论是否带有扩展名都要进行测试,因为它基本上会拒绝任何非dicom文件。

// Global variables

let picker = document.getElementById('picker');
let listing = document.getElementById('listing');
let progress_text = document.getElementById('progress_text');
let preprocess_notice = document.getElementById('preprocess_notice');
let results = document.getElementById('uploadresults');
let box = document.getElementById('box');
let elem = document.getElementById("myBar");
let loader = document.getElementById("loader");
let userid = document.getElementById("userid").value;
var anon_normal = document.getElementById("anon_normal").value;
var requestcounter;
var responsecounter;
var total;
// var excludedextensions = [".exe",".zip",".pdf",".jpg",".jpeg",".png",".gif",".doc",".docx", ".xml"];
var parsedepoch;
var datetimestamp;
var skipotherrequests;
var counts;

 
function checkDicomMime(file) {

  const fileReader = new FileReader();
  return new Promise((resolve, reject) => {
	var blob = file.slice(0, 132); //read enough bytes to get the DCM header info
	fileReader.readAsArrayBuffer(blob);
    //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;
        counts.process++;
      }
      else {
      	counts.omit++;
      }

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


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

	results.innerHTML = "";
	// Reset previous upload progress
    elem.style.width = "0px";
    listing.innerHTML = "";
    // Display image animation
    loader.style.display = "block";
    loader.style.visibility = "visible";
    preprocess_notice.innerHTML = "";
    //
	counts = {
		process:0,
		omit:0
	};
	requestcounter = 0;
	responsecounter = 0;
	total = 0;
	skipotherrequests = 0;
	const parsedepoch = new Date().toISOString().match(/(\d{4}\-\d{2}\-\d{2})T(\d{2}:\d{2}:\d{2})/);
	const datetimestamp = parsedepoch[1] + "-" + parsedepoch[2].replace(/:/g, "-");
	//alert(datetimestamp);
	picker.setAttribute('data-timestamp', datetimestamp);
	
	// Reset previous upload progress
    elem.style.width = "0px";
    listing.innerHTML = "";
    // Display image animation
    
    loader.style.display = "block";
    loader.style.visibility = "visible";
    
	let files = Array.from(picker.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 += '<li>' +result.fileName + '</li>', '')

  	total = counts.process; 
  	if (counts.omit > 0) preprocess_notice.innerHTML = '<div style = "color:red;">Omitting ' + counts.omit + ' file(s) that did not pass criteria"</div><ol>' + badlist + '</ol>';
  	
    for (let result of processList) {
		const file = result.file;
		const type = file.type;
		//console.log(result);
		if (!result.isBadFile) {
		//console.log('sending file', file)
		sendFile(file, datetimestamp, total, type);
		}
	}

});

statusitem = function(file, status) {

	let html = '<li><span>' + file.name + '</span><span>' + file.size + ' bytes</span><span>' + file.type + '</span><span>' + status + '</span></li>';
	return html;
}

// Function to send a file, call PHP backend

var sendFile = function(file, timestamp, total, type) {

	if (skipotherrequests == 0) {
	//console.log(file);
    const formData = new FormData();
    // Set post variables 
    requestcounter = requestcounter + 1;
    formData.set('method', "UploadFolder"); // One object file
    formData.set('timestamp', timestamp); // One object file
    formData.set('counter', requestcounter);
    formData.set('total', total); 
    formData.set('anon_normal', anon_normal); 
    formData.set('userid', userid); 
    formData.set('type', type); 
    formData.set('webkitpath', file.webkitRelativePath); // One object file
    formData.set('file', file); // One object file
	//console.log(file);
 	
    const request = new XMLHttpRequest();

    request.responseType = 'json';

    // HTTP onload handler
    
    request.onload = function() {
    	
        if (request.readyState === request.DONE) {
        
            if (request.status === 200) {
            
            	progress_text.innerHTML = file.name + " (" + (responsecounter + 1) + " of " + total + " ) ";
                //console.log(request.response);
				if (request.response.status != "Uploaded" || request.response.status != "Done" ) {
				skipotherrequests = 1;
				}
                // Add file name to list
                
                item = statusitem(request.response.file, request.response.file.status);
                listing.insertAdjacentHTML('beforeend', item);
                
				responsecounter++;
                // progress_text.innerHTML = request.response.file.name + " (" + responsecounter + " of " + total + " ) ";

                // Show percentage
                box.innerHTML = Math.min(responsecounter / total * 100, 100).toFixed(2) + "%";

                // Show progress bar
                elem.innerHTML = Math.round(responsecounter / total * 100, 100) + "%";
                elem.style.width = Math.round(responsecounter / total * 100) + "%";
                
                if (responsecounter >= total) {
                progress_text.innerHTML = "Sending " + total + " file(s) is done!";
                loader.style.display = "none";
                loader.style.visibility = "hidden";
           		}
           		 if (request.response.file.status == "Done") {
                	results.innerHTML = request.response.results;
                }

            }
            else {
            	skipotherrequests = 1;
            	//alert("error with AJAX requests");
            }
        }
    }

    // Do request, Sent off to the PHP Controller for processing
    
    request.open("POST", 'OrthancDevController.php');
    request.send(formData);
    }
    else {
    	// already aborted, probably never gets here because all of the requests are probably sent before skipotherrequests gets set to 1.
    }
}
code {
    font-family: Roboto Mono, monospace;
    font-size: 90%;
}

.picker {
    background-color: #eee;
    padding: 1em;
}


#box {
    color: #005aa0;
    font-size: 2rem;
    font-weight: bold;
    font-size:20px;
}

#myProgress {
    width: 100%;
    height: 30px;
    background-color: #ddd;
    border-radius: 5px;
}

#myBar {
    width: 1%;
    height: 30px;
    /* background-color: #4CAF50; */
    background-color: #e24718;
    text-align: center;
    vertical-align: middle;
    font-weight: bold;
    border-radius: 5px;
}

#loader {
    display: none;
    visibility: hidden;
}
#preprocess_notice {
text-align: left;
width: max-content;
margin: auto auto;

}

.dz-message  {
border-style:dotted;
padding:30px;
}
#ZipUpload {
background:white;

}
#dicomuploader {
background:white;
text-align:center;

}
#uploadinstructions {
text-align: left;
margin: 0 10px 0 10px;
}
#listing {

height: 100px;
overflow: scroll;
margin: auto;
padding: 10px 20px 10px 20px;
list-style-position: inside;

}
#listing li span, #statusheader span {

	display:inline-block;
	overflow:hidden;
	text-overflow: ellipsis;
	border:1px solid black;
	border-collapse:collapse;
	height: 20px;
	white-space: nowrap;
	padding: 0 5px 0 5px;
}
#listing li span:first-child, #statusheader span:first-child {

	width:150px;
	text-align:left;
}
#listing li span:nth-child(2), #statusheader span:nth-child(2) {
	width:100px;
	text-align:right;

}
#listing li span:nth-child(3), #statusheader span:nth-child(3) {
	width:150px;
	text-align:left;
}
#listing li span:nth-child(4), #statusheader span:nth-child(4) {
	width:200px;
	text-align:left;
}
#statusheader {
background:black;
color:white;
width: max-content;
margin: auto;
}
#statusheader span {
	border:1px solid white;
}
<div class="loadcontent" id = "dicomuploader">
	<h2>
		Upload Study To Server
	</h2>
	<p>
	In order to upload a study, please check the following:
	<ol id ="uploadinstructions">
	<li>You have a complete study (unpacked / unzipped ) in a folder on a CD or on your computer.</li>
	<li>Typically, there will be several folders with files there that end in .dcm, although they may not have a file extension.</li>
	<li>Using the button below, select the folder containing the files you need to upload, and then the files will upload.  If there is an error, a message will be displayed.  It typically takes a minute or two for the study to be available on the server.</li>
	<li>The entire folder should upload, including any contained subfolders.</li>
	</ol>
	</p>

	<h3>
		Choose Folder
	</h3>
	<div class="picker">
		<input type="file" id="picker" name="fileList" webkitdirectory multiple data-timestamp = "">
	</div>
	<!-- for the anon vs. normal upload, also userid and token, passed in -->
	
	<input type="hidden" id="anon_normal" name="anon_normal" value = "<?php echo $_GET['anon_normal'] ?>" >
	<input type="hidden" id="userid" name="userid" value = "<?php echo $_GET['userid'] ?>" >
	<input type="hidden" id="upload_auth_token" name="upload_auth_token" value = "<?php echo $_GET['upload_auth_token'] ?>" >

	<div>
		Percentage Processed
	</div>
	<span id="box">0%</span> 
	<div style="color:red;font-size:14px;">(there will be a pause before 100% while storing the study), please wait.</div>
	<h5>
		Percentage Uploaded
	</h5>
	<div id="myProgress">
		<div id="myBar"></div>
	</div>
	<h5>
		Sent File . . <span id = "progress_text"></span>
	</h5>
	<h3>
		Files Uploaded
	</h3>
<div id="preprocess_notice"></div> 
<div id = "statusheader"><span>File Name</span><span>File Size</span><span>MIME Type</span><span>Status</span></div>
	<ol id="listing"></ol> 
	<div id="uploadresults"></div>

<img id="loader" src="loader.gif">
</div>	
© www.soinside.com 2019 - 2024. All rights reserved.