typescript - 如何在最后一步之前异步所有上传的文件?
问题描述
我希望用户上传多个图像文件,应用程序读取所有图像文件并推送到一个数组中,然后全部完成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();
});
}
解决方案
你有正确的想法来使用 Promise,你只需要稍微不同地把事情联系起来。运行下面的代码片段并选择两个小文件进行测试!
// 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
and lastStep
。使用async
andawait
我们可以很容易地将两者合二为一——
// 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>
没有承诺
“第二个想法,我们在这里做的是每个文件都有一个Promise,然后等到全部解决。是否可以为所有文件做一个promise?”
Promises 的好处是它们是轻量级且可组合的,这意味着您可以从较小的异步计算中构建更大的异步计算。编写回调更具挑战性,因此更容易犯常见错误。如果我们愿意,我们可以将所有这些包装在一个 Promise 中,或者完全跳过 Promise -
// 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
指定一个 final -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
旧的和繁琐的回调模式已经很少需要了。
推荐阅读
- facebook-login - 无法从 facebook 响应加载图像
- ruby-on-rails - Rspec 请求规范 ActionController::RoutingError
- ios - 科尔多瓦 IOS 应用程序所有 onclick 和后退按钮不适用于最新的 IOS 11.3.1
- php - 对象中的 mysqli_fetch_assoc
- html - 科尔多瓦文件下载并使用科尔多瓦打开它
- java - 将 Curl -G 转换为 java 代码
- sql - 使用 UTL_FILE 包远程读写文本文件
- python - Python 究竟是如何检查列表的?
- python - 如何更新软件包哈希?
- javascript - Javascript 和 jQuery 都停止在 for 循环中删除选项