【问题标题】:Problem with Asynchronous Function, JS. Can I use Promises?异步函数的问题,JS。我可以使用 Promise 吗?
【发布时间】:2020-05-19 23:02:47
【问题描述】:

我有一些我认为现在不能正常工作的代码,因为我添加了一些东西来使用一些 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检查功能: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


    【解决方案1】:

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

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

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

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

    示例

    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>

    【讨论】:

    • 谢谢。我将其中的一些内容与包含 .dcm 文件和其他文件的实际文件夹进行了修改和测试。检查客户端的一个原因是避免上传非 dicom 文件,因为我们不需要这些文件,它们可能非常大或包含我们不想要的 .exe 文件。一些 dicom 文件没有 .dcm 扩展名,而且也没有。也适用于其他 MIME 类型。 PHP 可以检测实际的 MIME 类型服务器端,我们也在这样做。如果我现在想包含工作代码,我应该在我的问题中发布还是添加答案,即使接受了你的答案?
    • 另外,我不必使用 Promise 来通过 AJAX 实际发布文件,只需检测客户端的 MIME 类型。服务器可以处理快速连续地异步触发文件。这需要一些相当复杂的代码,因为它使用 DCMTK 或解析标头并将它们发送到 PACS。
    • 我认为您应该添加单独的答案,告诉您如何解决它。
    • 谢谢。我还必须添加一些内容来读取客户端文件的所需部分。 var blob = file.slice(0, 132); //读取文件的前4个字节 fileReader.readAsArrayBuffer(blob);这很复杂,但如果我只包括框架,可能不会太多。它确实非常快,因为服务器处理它们的速度非常快。另一种方法是仅上传一个 .zip 文件并在服务器端解压缩,但这是用于放射学研究,您会被诊所或医院刻录到 CD 上。这允许您上传 CD。
    • 发布了我的大部分代码,除了服务器端,但显示了预期响应。如果你有 dicom 文件(可在此处获得),你可以测试:medistim.com/dicom
    【解决方案2】:

    已经接受了答案,但发布了修改后的代码,现在似乎运行良好。如果您实际上有一个包含一些 .dcm 文件(带或不带文件扩展名)的文件夹,它应该排除任何不是真正的 .dcm 文件的文件,并且可以扩展为其他类型的文件,如另一篇文章中所述我参考了。

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

    GitHub Library to Detect MIME Client Side

    另外,如果你运行 sn-p,它会触发一堆带有 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>	

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 2019-08-18
      • 2014-06-14
      • 1970-01-01
      • 2022-01-03
      • 1970-01-01
      • 2018-10-04
      • 1970-01-01
      • 2015-10-21
      相关资源
      最近更新 更多