如何在最后一步之前异步所有上传的文件?
How to async all uploaded files before last step?
我希望用户上传多个图像文件,应用程序读取所有这些文件并推入一个数组,当全部完成后 lastStep()
<input type="file" (change) = "fileChangedEvent($event);" multiple/>
fileChangedEvent(event: any): void
{
for (let f of event.target.files)
{
var reader = new FileReader();
reader.readAsDataURL(f);
reader.onload= (event) => {this.arrayThumbnail.push(event.target.result);};
}
// when all done
lastStep(); // work on arrayThumbnail[]
}
但是 lastStep()
总是在 for ... of
循环之前 运行,导致它在空的 arrayThumbnail[]
上工作。以下 async/await
产生相同的结果:
fileChangedEvent(event: any): void
{
async() => {
for (let f of event.target.files)
{
var reader = new FileReader();
await reader.readAsDataURL(f);
await reader.onload= (event) => {this.arrayThumbnail.push(event.target.result);};
}
// when all done
lastStep(); // work on arrayThumbnail[]
}
}
显式 Promise
也不起作用:
myPromise(fs: any): Promise<number>
{
var bogus = new Promise<number>((resolve, reject) =>
{
for (let f of fs)
{
var reader = new FileReader();
reader.readAsDataURL(f);
reader.onload= (event) => {this.arrayThumbnail.push(event.target.result);};
};
resolve(1);
})
return bogus;
}
fileChangedEvent(event: any): void
{
this.myPromise(event.target.files).then(
x=>
{
lastStep();
});
}
你使用 promises 的想法是正确的,你只需要稍微改变一下。 运行 下面的代码片段和 select 两个小文件来测试它!
// onChange listener
function onChange(event) {
const promises = []
for (const file of event.target.files)
promises.push(readFile(file))
Promise.all(promises).then(lastStep)
}
// read one file
function readFile(f) {
return new Promise((resolve, reject) => {
const reader = new FileReader()
reader.readAsDataURL(f)
reader.onload = event => resolve(event.target.result)
reader.onerror = reject
})
}
// example last step
function lastStep(data) {
console.log("last step")
console.log(`uploaded ${data.length} files`)
for (const blob of data) {
const pre = document.createElement("pre")
pre.textContent = blob
document.body.appendChild(pre)
}
}
document.forms.myapp.files.addEventListener("change", onChange)
<form id="myapp">
<input type="file" name="files" multiple>
</form>
一旦您明白了这一点,就知道我们可以更轻松地重写 onChange
-
// onChange listener simplified
function onChange(event) {
Promise.all(Array.from(event.target.files, readFile)).then(lastStep)
}
现在真的没有理由将 onChange
和 lastStep
分开了。使用 async
和 await
我们可以很容易地将两者合二为一 -
// onChange listener
async function onChange(event) {
const data = await Promise.all(Array.from(event.target.files, readFile))
console.log(`uploaded ${data.length} files`)
for (const blob of data) {
const pre = document.createElement("pre")
pre.textContent = blob
document.body.appendChild(pre)
}
}
// read one file
function readFile(f) {
return new Promise((resolve, reject) => {
const reader = new FileReader()
reader.readAsDataURL(f)
reader.onload = event => resolve(event.target.result)
reader.onerror = reject
})
}
document.forms.myapp.files.addEventListener("change", onChange)
<form id="myapp">
<input type="file" name="files" multiple>
</form>
没有承诺
"2nd thought, what we're doing here is each file has one Promise, then wait till all settled. Is it possible to do one promise for all files?"
Promises 的好处是它们是轻量级和可组合的,这意味着您可以从较小的异步计算中构建较大的异步计算。编写回调更具挑战性,因此更容易犯常见错误。如果我们愿意,我们可以将所有这些包装在一个承诺中,或者完全跳过承诺 -
// onChange listener
function onChange(event) {
const data = []
const noEror = true
// for each file, f
for (const f of event.target.files) {
// readFile with callback
readFile(f, (err, result) => {
// handle errors
// only call error handler a maximum of one time
if (err && noError) {
noError = false
return handleError(err)
}
// add individual result to data
data.push(result)
// once data.length is equal to number of input files
// then proceed to the last step
if (data.length == event.target.files.length)
return lastStep(data)
})
}
}
// read one file
function readFile(f, callback) {
const reader = new FileReader()
reader.readAsDataURL(f)
reader.onload = event => callback(null, event.target.result)
reader.onerror = err => callback(err)
}
function handleError (err) { ... }
function lastStep (data) { ... }
如您所见,本应是一个简单的过程,但要写的内容却很多。每次我们需要类似的功能时,所有这些工作都会重复。为了解决这个问题,我们可以编写一个通用的 asyncEach
实用程序,只要我们需要使用异步操作 f
遍历数组,就可以使用它,并指定一个最终的 callback
当所有操作完成-
// asyncEach generic utility
function asyncEach(arr, f, callback, data = []) {
if (arr.length == 0)
callback(null, data)
else
f(arr[0], (err, result) =>
err
? callback(err)
: asyncEach(arr.slice(1), f, callback, [...data, result])
)
}
// read one file
function readFile(f, callback) {
const reader = new FileReader()
reader.readAsDataURL(f)
reader.onload = event => callback(null, event.target.result)
reader.onerror = err => callback(err)
}
// onChange listener
function onChange(event) {
asyncEach(Array.from(event.target.files), readFile, (err, data) => {
if (err) return console.error(err)
console.log(`uploaded ${data.length} files`)
for (const blob of data) {
const pre = document.createElement("pre")
pre.textContent = blob
document.body.appendChild(pre)
}
})
}
document.forms.myapp.files.addEventListener("change", onChange)
<form id="myapp">
<input type="file" name="files" multiple>
</form>
我认为这是一个很好的练习,但大多数人不会尝试编写自己的 asyncEach
,而对这类东西的普遍需求是 async 等流行库的基础。然而,由于 Promise 和新的 async
-await
语法的广泛成功,几乎不需要这些旧的和繁琐的回调模式。
我希望用户上传多个图像文件,应用程序读取所有这些文件并推入一个数组,当全部完成后 lastStep()
<input type="file" (change) = "fileChangedEvent($event);" multiple/>
fileChangedEvent(event: any): void
{
for (let f of event.target.files)
{
var reader = new FileReader();
reader.readAsDataURL(f);
reader.onload= (event) => {this.arrayThumbnail.push(event.target.result);};
}
// when all done
lastStep(); // work on arrayThumbnail[]
}
但是 lastStep()
总是在 for ... of
循环之前 运行,导致它在空的 arrayThumbnail[]
上工作。以下 async/await
产生相同的结果:
fileChangedEvent(event: any): void
{
async() => {
for (let f of event.target.files)
{
var reader = new FileReader();
await reader.readAsDataURL(f);
await reader.onload= (event) => {this.arrayThumbnail.push(event.target.result);};
}
// when all done
lastStep(); // work on arrayThumbnail[]
}
}
显式 Promise
也不起作用:
myPromise(fs: any): Promise<number>
{
var bogus = new Promise<number>((resolve, reject) =>
{
for (let f of fs)
{
var reader = new FileReader();
reader.readAsDataURL(f);
reader.onload= (event) => {this.arrayThumbnail.push(event.target.result);};
};
resolve(1);
})
return bogus;
}
fileChangedEvent(event: any): void
{
this.myPromise(event.target.files).then(
x=>
{
lastStep();
});
}
你使用 promises 的想法是正确的,你只需要稍微改变一下。 运行 下面的代码片段和 select 两个小文件来测试它!
// onChange listener
function onChange(event) {
const promises = []
for (const file of event.target.files)
promises.push(readFile(file))
Promise.all(promises).then(lastStep)
}
// read one file
function readFile(f) {
return new Promise((resolve, reject) => {
const reader = new FileReader()
reader.readAsDataURL(f)
reader.onload = event => resolve(event.target.result)
reader.onerror = reject
})
}
// example last step
function lastStep(data) {
console.log("last step")
console.log(`uploaded ${data.length} files`)
for (const blob of data) {
const pre = document.createElement("pre")
pre.textContent = blob
document.body.appendChild(pre)
}
}
document.forms.myapp.files.addEventListener("change", onChange)
<form id="myapp">
<input type="file" name="files" multiple>
</form>
一旦您明白了这一点,就知道我们可以更轻松地重写 onChange
-
// onChange listener simplified
function onChange(event) {
Promise.all(Array.from(event.target.files, readFile)).then(lastStep)
}
现在真的没有理由将 onChange
和 lastStep
分开了。使用 async
和 await
我们可以很容易地将两者合二为一 -
// onChange listener
async function onChange(event) {
const data = await Promise.all(Array.from(event.target.files, readFile))
console.log(`uploaded ${data.length} files`)
for (const blob of data) {
const pre = document.createElement("pre")
pre.textContent = blob
document.body.appendChild(pre)
}
}
// read one file
function readFile(f) {
return new Promise((resolve, reject) => {
const reader = new FileReader()
reader.readAsDataURL(f)
reader.onload = event => resolve(event.target.result)
reader.onerror = reject
})
}
document.forms.myapp.files.addEventListener("change", onChange)
<form id="myapp">
<input type="file" name="files" multiple>
</form>
没有承诺
"2nd thought, what we're doing here is each file has one Promise, then wait till all settled. Is it possible to do one promise for all files?"
Promises 的好处是它们是轻量级和可组合的,这意味着您可以从较小的异步计算中构建较大的异步计算。编写回调更具挑战性,因此更容易犯常见错误。如果我们愿意,我们可以将所有这些包装在一个承诺中,或者完全跳过承诺 -
// onChange listener
function onChange(event) {
const data = []
const noEror = true
// for each file, f
for (const f of event.target.files) {
// readFile with callback
readFile(f, (err, result) => {
// handle errors
// only call error handler a maximum of one time
if (err && noError) {
noError = false
return handleError(err)
}
// add individual result to data
data.push(result)
// once data.length is equal to number of input files
// then proceed to the last step
if (data.length == event.target.files.length)
return lastStep(data)
})
}
}
// read one file
function readFile(f, callback) {
const reader = new FileReader()
reader.readAsDataURL(f)
reader.onload = event => callback(null, event.target.result)
reader.onerror = err => callback(err)
}
function handleError (err) { ... }
function lastStep (data) { ... }
如您所见,本应是一个简单的过程,但要写的内容却很多。每次我们需要类似的功能时,所有这些工作都会重复。为了解决这个问题,我们可以编写一个通用的 asyncEach
实用程序,只要我们需要使用异步操作 f
遍历数组,就可以使用它,并指定一个最终的 callback
当所有操作完成-
// asyncEach generic utility
function asyncEach(arr, f, callback, data = []) {
if (arr.length == 0)
callback(null, data)
else
f(arr[0], (err, result) =>
err
? callback(err)
: asyncEach(arr.slice(1), f, callback, [...data, result])
)
}
// read one file
function readFile(f, callback) {
const reader = new FileReader()
reader.readAsDataURL(f)
reader.onload = event => callback(null, event.target.result)
reader.onerror = err => callback(err)
}
// onChange listener
function onChange(event) {
asyncEach(Array.from(event.target.files), readFile, (err, data) => {
if (err) return console.error(err)
console.log(`uploaded ${data.length} files`)
for (const blob of data) {
const pre = document.createElement("pre")
pre.textContent = blob
document.body.appendChild(pre)
}
})
}
document.forms.myapp.files.addEventListener("change", onChange)
<form id="myapp">
<input type="file" name="files" multiple>
</form>
我认为这是一个很好的练习,但大多数人不会尝试编写自己的 asyncEach
,而对这类东西的普遍需求是 async 等流行库的基础。然而,由于 Promise 和新的 async
-await
语法的广泛成功,几乎不需要这些旧的和繁琐的回调模式。