Javascript 承诺与 FileReader()

声明:本页面是StackOverFlow热门问题的中英对照翻译,遵循CC BY-SA 4.0协议,如果您需要使用它,必须同样遵循CC BY-SA许可,注明原文地址和作者信息,同时你必须将它归于原作者(不是我):StackOverFlow 原文地址: http://stackoverflow.com/questions/34495796/
Warning: these are provided under cc-by-sa 4.0 license. You are free to use/share it, But you must attribute it to the original authors (not me): StackOverFlow

提示:将鼠标放在中文语句上可以显示对应的英文。显示中英文
时间:2020-08-23 16:26:17  来源:igfitidea点击:

Javascript Promises with FileReader()

javascriptpromisefilereadersynchronous

提问by Zahid Saeed

I have the following HTML Code:

我有以下 HTML 代码:

<input type='file' multiple>

And Here's my JS Code:

这是我的 JS 代码:

var inputFiles = document.getElementsByTagName("input")[0];
inputFiles.onchange = function(){
    var fr = new FileReader();
    for(var i = 0; i < inputFiles.files.length; i++){
        fr.onload = function(){
            console.log(i) // Prints "0, 3, 2, 1" in case of 4 chosen files
        }
    }
    fr.readAsDataURL(inputFiles.files[i]);
}

So my question is, how can I make this loop synchronous ?That is first wait for the file to finish loading then move on to the next file. Someone told me to use JS Promises. But I can't make it to work. Here's what I'm trying:

所以我的问题是,我怎样才能使这个循环同步?即首先等待文件完成加载然后移动到下一个文件。有人告诉我使用 JS Promises。但我无法让它工作。这是我正在尝试的:

var inputFiles = document.getElementsByTagName("input")[0];
inputFiles.onchange = function(){
    for(var i = 0; i < inputFiles.files.length; i++){
        var fr = new FileReader();
        var test = new Promise(function(resolve, reject){
            console.log(i) // Prints 0, 1, 2, 3 just as expected
            resolve(fr.readAsDataURL(inputFiles.files[i]));
        });
        test.then(function(){
            fr.onload = function(){
                console.log(i); // Prints only 3
            }
        });
    };
}

Thanks in advance...

提前致谢...

回答by mido

If you want to do it sequentially( not synchronously) using Promises, you could do something like:

如果您想使用 Promise 顺序(非同步)执行此操作,您可以执行以下操作:

var inputFiles = document.getElementsByTagName("input")[0];
inputFiles.onchange = function(){
  var promise = Promise.resolve();
  inputFiles.files.map( file => promise.then(()=> pFileReader(file)));
  promise.then(() => console.log('all done...'));
}

function pFileReader(file){
  return new Promise((resolve, reject) => {
    var fr = new FileReader();  
    fr.onload = resolve;  // CHANGE to whatever function you want which would eventually call resolve
    fr.readAsDataURL(file);
  });
}

回答by Jens Lincke

We modified midos answerto get it to work the following:

我们修改了midos 答案以使其工作如下:

function readFile(file){
  return new Promise((resolve, reject) => {
    var fr = new FileReader();  
    fr.onload = () => {
      resolve(fr.result )
    };
    fr.readAsText(file.blob);
  });
}

回答by T.J. Crowder

The nature of FileReaderis that you cannot make its operation synchronous.

本质FileReader是您不能使其操作同步。

I suspect you don't really need or want it to be synchronous, just that you want to get the resulting URLs correctly. The person suggesting using promises was probably right, but not because promises make the process synchronous (they don't), but because they provide standardized semantics for dealing with asynchronous operations (whether in paralle or in series):

我怀疑您并不真正需要或希望它是同步的,只是您想正确获取结果 URL。建议使用 Promise 的人可能是对的,但不是因为 Promise 使过程同步(他们没有),而是因为它们提供了用于处理异步操作(无论是并行还是串行)的标准化语义:

Using promises, you'd start with a promise wrapper for readAsDataURL(I'm using ES2015+ here, but you can convert it to ES5 with a promise library instead):

使用承诺,您将从承诺包装器开始readAsDataURL(我在这里使用 ES2015+,但您可以使用承诺库将其转换为 ES5):

function readAsDataURL(file) {
    return new Promise((resolve, reject) => {
        const fr = new FileReader();
        fr.onerror = reject;
        fr.onload = function() {
            resolve(fr.result);
        }
        fr.readAsDataURL(file);
    });
}

Then you'd use the promise-based operations I describe in this answerto read those in parallel:

然后,您将使用我在此答案中描述的基于 Promise 的操作来并行读取这些操作:

Promise.all(Array.prototype.map.call(inputFiles.files, readAsDataURL))
.then(urls => {
    // ...use `urls` (an array) here...
})
.catch(error => {
    // ...handle/report error...
});

...or in series:

...或系列:

Array.prototype.reduce.call(inputFiles.files, (p, file) => p.then(() => readAsDataURL(file).then(url => {
    // ...use `url` here...
})), Promise.resolve())
.catch(error => {
    // ...handle/report error...
});

Inside an ES2017 asyncfunction, you could use await. It doesn't do much more the parallel version:

在 ES2017async函数中,您可以使用await. 它并没有做更多的并行版本:

// Inside an `async` function
try {
    const urls = await Promise.all(Array.prototype.map.call(inputFiles.files, readAsDataURL));
} catch (error) {
    // ...handle/report error...
}

...but it makes the series version muchsimpler and clearer:

...但它使系列版本简单、更清晰:

// Inside an `async` function
try {
    for (let i = 0; i < inputFiles.files.length; ++i) {
        const file = inputFiles.files[i];
        const url = await readAsDataURL(file);
        // ...use `url` here...
    }
} catch (error) {
    // ...handle/report error...
}

Without promises, you'd do this by keeping track of how many outstanding operations you have so you know when you're done:

如果没有承诺,您可以通过跟踪您有多少未完成的操作来做到这一点,以便您知道何时完成:

var inputFiles = document.getElementsByTagName("input")[0];
inputFiles.onchange = function(){
    var data = [];      // The results
    var pending = 0;    // How many outstanding operations we have

    // Schedule reading all the files (this finishes before the first onload
    // callback is allowed to be executed)
    Array.prototype.forEach.call(inputFiles.files, function(file, index) {
        // Read this file, remember it in `data` using the same index
        // as the file entry
        var fr = new FileReader();
        fr.onload = function() {
            data[index] = fr.result;
            --pending;
            if (pending == 0) {
                // All requests are complete, you're done
            }
        }
        fr.readAsDataURL(file);
        ++pending;
    });
}

Or if you want for some reason to read the files sequentially(but still asynchronously), you can do that by scheduling the next call only when the previous one is complete:

或者,如果您出于某种原因想要顺序读取文件(但仍然是异步的),您可以通过仅在前一个调用完成时安排下一个调用来实现:

// Note: This assumes there is at least one file, if that
// assumption isn't valid, you'll need to add an up-front check
var inputFiles = document.getElementsByTagName("input")[0];
inputFiles.onchange = function(){
    var index = 0;

    readNext();

    function readNext() {
        var file = inputFiles.files[index++];
        var fr = new FileReader();
        fr.onload = function() {
            // use fr.result here
            if (index < inputFiles.files.length) {
                // More to do, start loading the next one
                readNext();
            }
        }
        fr.readAsDataURL(file);
    }
}

回答by Kamil Kie?czewski

I upgrade Jens Lincke answerby add working example and introduce async/await syntax

我通过添加工作示例来升级Jens Lincke 答案并引入 async/await 语法

function readFile(file) {
  return new Promise((resolve, reject) => {
    let fr = new FileReader();
    fr.onload = x=> resolve(fr.result);
    fr.readAsDataURL(file) // or readAsText(file) to get raw content
})}

function readFile(file) {
  return new Promise((resolve, reject) => {
    let fr = new FileReader();
    fr.onload = x=> resolve(fr.result);
    fr.readAsDataURL(file) // or readAsText(file) to get raw content
})}

async function load(e) {
  for(let [i,f] of [...e.target.files].entries() ){
    msg.innerHTML += `<h1>File ${i}: ${f.name}</h1>`;
    let p = document.createElement("pre");
    p.innerText += await readFile(f);
    msg.appendChild(p);
  }
}
<input type="file" onchange="load(event)" multiple />
<div id="msg"></div>

回答by JasonZiolo

Here is another modification to Jens' answer (piggybacking off Mido's answer) to additionally check the file size:

这是对 Jens 的回答的另一个修改(捎带 Mido 的回答)以额外检查文件大小:

function readFileBase64(file, max_size){
        max_size_bytes = max_size * 1048576;
        return new Promise((resolve, reject) => {
            if (file.size > max_size_bytes) {
                console.log("file is too big at " + (file.size / 1048576) + "MB");
                reject("file exceeds max size of " + max_size + "MB");
            }
            else {
            var fr = new FileReader();  
            fr.onload = () => {
                data = fr.result;
                resolve(data)
            };
            fr.readAsDataURL(file);
            }
        });
    }