forEach 循环可以与 Promise 一起使用,但是会导致 await 失效,得用for of
替代。
场景复现:
uniapp u-upload图片上传h5网页 ,uview不支持网页压缩加了个方法
后来才发现循环中,上传方法时一直没等压缩完就先跑了,导致一直上传空路径,自然返回也为空。
原理
forEach 是一个用于遍历数组的方法,它会对数组中的每个元素都执行一个提供的回调函数。然而,forEach 并不会等待每个回调函数的异步操作完成,它会继续执行下一个循环迭代,这可能会导致问题。如果在 forEach 循环中使用 await,由于 forEach 本身不会等待异步操作完成,await 可能无法按预期工作。
示例代码:
items.forEach(async item => {
// Some asynchronous operation
await doSomethingAsync(item);
console.log(`Processed ${item}`);
});
console.log("Loop finished");
在上面的代码中,doSomethingAsync 是一个异步操作,但是由于 forEach 不会等待每个回调函数内部的异步操作完成,"Loop finished" 可能会在异步操作完成之前就被打印出来。
为了解决这个问题,您可以使用 for...of 循环与 await 结合,或者使用 Promise.all 来等待所有异步操作完成:
使用 for...of 循环:
for (const item of items) {
await doSomethingAsync(item);
console.log(`Processed ${item}`);
}
console.log("Loop finished");
使用 Promise.all:
const items = [1, 2, 3, 4];
await Promise.all(items.map(async item => {
await doSomethingAsync(item);
console.log(`Processed ${item}`);
}));
console.log("Loop finished");
这两种方法都会确保在异步操作完成后再进行下一次迭代,以保持 await 的正常工作。
额外:
uniapp压缩网页图片代码
methods:{
base64实际为图片url地址
dealImage(base64) {
return new Promise(resolve => {
var newImage = new Image()
var quality = 0.7 //压缩系数0-1之间
if (base64.length / 1024 > 1024 * 2) {
quality = 0.5
}
if (base64.length / 1024 > 1024 * 5) {
quality = 0.3
}
newImage.src = base64
newImage.setAttribute('crossOrigin', 'Anonymous') //url为外域时需要
var imgWidth, imgHeight
let self = this
newImage.onload = function() {
imgWidth = this.width
imgHeight = this.height
var canvas = document.createElement('canvas')
var ctx = canvas.getContext('2d')
canvas.width = imgWidth / 2
canvas.height = imgHeight / 2
ctx.clearRect(0, 0, canvas.width, canvas.height)
ctx.drawImage(this, 0, 0, imgWidth / 2, imgHeight / 2)
var base64 = canvas.toDataURL('image/jpeg', quality) //压缩语句
// base64转为url
let file = self.convertBase64UrlToFile(base64)
resolve(file.path)
}
})
},
convertBase64UrlToFile(base64) {
let urlData = base64
let type = base64.type
let contentName = new Date().getTime()
let bytes = null
if (urlData.split(',').length > 1) {
//是否带前缀
bytes = window.atob(urlData.split(',')[1]) // 去掉url的头,并转换为byte
} else {
bytes = window.atob(urlData)
}
// 处理异常,将ascii码小于0的转换为大于0
let ab = new ArrayBuffer(bytes.length)
let ia = new Uint8Array(ab)
for (let i = 0; i < bytes.length; i++) {
ia[i] = bytes.charCodeAt(i)
}
let result = new Blob([ab], {
type: type
})
let result1 = new File([result], contentName, {
type: type
})
result1.path = window.URL.createObjectURL(result)
return result1
},
// 使用方式用到了promise 等待压缩完成和await 配套(async/await成套存在,相当于标识符/语法糖,相当于规定了+时两边相加,这里相当于看到就知道是promise 可以等待执行)
async xx() {
const newUrl = await this.dealImage(item.url)
}
}