时间:2022-10-20 作者:悬浮的青春 分类: javascript
请不要在说 foreach 是一个异步的了!
forEach 执行异步代码
可能你遇到的情况是 forEach 中执行的都是异步函数
,你想在里面逐个执行出来!但是不行!比如下面的代码
const sleep = (ms) => {
return new Promise((resolve) => {
setTimeout(resolve, ms)
})
}
const arr = [
() => console.log("start"),
() => sleep(1000),
() => console.log(1),
() => sleep(1000),
() => console.log(2),
() => sleep(1000),
() => console.log(3),
() => sleep(1000),
() => console.log("end")
]
arr.forEach(async fn => {
await fn()
})
console.log("for循环执行完")
这里 await 的 ‘跳出当前线程的操作,因为在for循环里,
所以每次跳出也是跳出当前这次循环,但并没有跳到for循环外面。
期待的结果是先打印 start, 然后每隔一秒往下执行一次,直到 end,但是你执行会发现,这些 console 会一次瞬间执行完成!
那为什么会这样呢?我们可以去 mdn 找到 forEach 源码
// Production steps of ECMA-262, Edition 5, 15.4.4.18
// Reference: http://es5.github.io/#x15.4.4.18
if (!Array.prototype.forEach) {
Array.prototype.forEach = function(callback, thisArg) {
var T, k;
if (this == null) {
throw new TypeError(' this is null or not defined');
}
// 1. Let O be the result of calling toObject() passing the
// |this| value as the argument.
var O = Object(this);
// 2. Let lenValue be the result of calling the Get() internal
// method of O with the argument "length".
// 3. Let len be toUint32(lenValue).
var len = O.length >>> 0;
// 4. If isCallable(callback) is false, throw a TypeError exception.
// See: http://es5.github.com/#x9.11
if (typeof callback !== "function") {
throw new TypeError(callback + ' is not a function');
}
// 5. If thisArg was supplied, let T be thisArg; else let
// T be undefined.
if (arguments.length > 1) {
T = thisArg;
}
// 6. Let k be 0
k = 0;
// 7. Repeat, while k < len
while (k < len) {
var kValue;
// a. Let Pk be ToString(k).
// This is implicit for LHS operands of the in operator
// b. Let kPresent be the result of calling the HasProperty
// internal method of O with argument Pk.
// This step can be combined with c
// c. If kPresent is true, then
if (k in O) {
// i. Let kValue be the result of calling the Get internal
// method of O with argument Pk.
kValue = O[k];
// ii. Call the Call internal method of callback with T as
// the this value and argument list containing kValue, k, and O.
callback.call(T, kValue, k, O);
}
// d. Increase k by 1.
k++;
}
// 8. return undefined
};
}
关键代码在这里
foreach 内部呢是一个 while 循环,然后再内部去执行回调函数!你可以看到并没有对内部异步进行什么处理~
async fn => {
await fn()
}
相当于每次循环,都只是回调执行了外层的 fn, 执行就完事了,而没有对内部的 await 做一些操作,其实就是在循环中没有去等待执行 await 的结果,所以里面的异步 sleep, 还是放到异步队列去等待同步执行完成后再去执行,也就是先打印再去 sleep, 所以没有 sleep 的效果
如果直接用 for 循环去处理,那么就是针对每一次循环去做了一个异步的 await,
const sleep = (ms) => {
return new Promise((resolve) => {
setTimeout(resolve, ms)
})
}
const arr = [
() => console.log("start"),
() => sleep(1000),
() => console.log(1),
() => sleep(1000),
() => console.log(2),
() => sleep(1000),
() => console.log(3),
() => sleep(1000),
() => console.log("end")
]
async function run(arr) {
for (let i = 0; i < arr.length; i++) {
await arr[i]()
}
}
run(arr)
(async function(){
})()//或者使用匿名函数!!!
写一个自己的 forEach
其实就是把 forEach 内部实现改成 for 循环,让每次循环都能捕捉执行 await, 而不是外层的 async 函数
Array.prototype.asyncForEach = async function (callback, args) {
const _arr = this, // 因为调用的方式 [1,2,3].asyncForEach this指向数组
isArray = Array.isArray(_arr), //判断调用者是不是数组
_args = args ? Object(args) : window //对象化
if (!isArray) {
throw new TypeError("the caller must be a array type!")
}
for (let i = 0; i < _arr.length; i++) {
await callback.call(_args,_arr[i])
}
}
const sleep = (ms) => {
return new Promise((resolve) => {
setTimeout(resolve, ms)
})
}
const arr = [
() => console.log("start"),
() => sleep(1000),
() => console.log(1),
() => sleep(1000),
() => console.log(2),
() => sleep(1000),
() => console.log(3),
() => sleep(1000),
() => console.log("end")
]
arr.asyncForEach(async(fn)=>{
await fn()
})
测试一下,成功的!
let arr = [1, 2, 3, 4, 5, 6, 7];
let arr2 = [];
arr.forEach((item) => {
setTimeout(() => {
arr2.push(item);
}, 1000);
});
console.log(JSON.parse(JSON.stringify(arr2))); // []
如上:当forEach内部处理异步操作时,则forEach同时也处于异步状态,并不会阻塞进程,而是让下面的语句先执行
即如果在使用forEach遍历数组时,forEach内有异步操作,那么后面的代码执行是不会等待forEach的执行结果,但是很明显我们想要拿到的是forEach结束后的数据,此时我们只需要将forEach写到promise里,即使forEach处于同步状态。
把每次的循环都包裹到一个Promise里,形成一个Promise队列(asyncFuns),最后使用Promise.all来判断是否全部执行完毕
let arr = [1, 2, 3, 4, 5, 6, 7];
let arr2= [];
function pro(item) {
return new Promise((resolve, reject) => {
setTimeout(() => {
arr2.push(item);
resolve();
}, 10000);
});
}
let asyncFuns = [];
arr.forEach((item) => {
asyncFuns.push(pro(item));
});
Promise.all(asyncFuns).then(() => {
console.log('res', arr2); // 十秒钟后打印:res (7) [1, 2, 3, 4, 5, 6, 7]
});
或者使用原生的for循环
(async function () {
for (let index = 0; index < flisdata.length; index++) {
const item = flisdata[index];
if (item.FLIID) {
let imgdataBase64 = await that.getFileById(item.FLIID);
that.loading = false;
if (!imgdataBase64) {
// return
} else {
that.$set(item, 'imgsrc', flisdata[index].FLIOSSKEY + ',' + imgdataBase64);//注意这里不是给vm的一个属性赋值,而只是利用了$set 这个函数的赋值作用。不写这个直接用 点语法也行;
//Vue.set(vm.items, indexOfItem, newValue) 采用这种直接从vm.到对象 会不会就不用写下面的that.filedata = flisdata了 ??
that.loadingArray[index] = false;
}
// 获取当前页面的路径
// let pathheader = window.location.protocol;
// let pathName = window.location.host;
// let paths = pathheader + '//' + pathName;
}
that.filedata = flisdata;//写这就是为了 每次获取item的src后,立刻重新给vm的filedata 赋值,然后更新;这样就做到了每异步获取一个附件轮播图就显示一个附件的效果
}
})();
let arr = [1, 2, 3, 4, 5, 6, 7];
let arr2 = [];
function pro(item) {
return new Promise((resolve, reject) => {
setTimeout(() => {
arr2.push(item);
console.log(arr2);
resolve();
}, 1000);
});
}
arr.forEach(async (item) => {
await pro(item);
console.log('这里是等待每一次循环结束后的操作');
});
forEach方法用于调用数组的每个元素,并将元素传递给回调函数
map方法返回一个新数组,数组中的元素为原始数组元素调用函数处理后的值
由于forEach执行并不返回任何数据,则无法使用Promise.all方法进行循环是否结束的判断,于是我们想到了使用map+Promise.all来处理这种情况
let arr = [1, 2, 3, 4, 5, 6, 7];
let arr2 = [];
function pro(item) {
return new Promise((resolve, reject) => {
setTimeout(() => {
arr2.push(item);
console.log(arr2);
resolve();
}, 1000);
});
}
Promise.all(
arr.map((item) => {
return new Promise(async (resolve, reject) => {
await pro(item);
console.log('这里是等待每一次循环结束后的操作');
resolve();
});
})
).then(() => {
console.log('res', arr2);
});
但是实际上forEach也可以手动设定条件来判断是否遍历结束,由于forEach遍历为顺序执行遍历,所以我们可以使用当前项的index值来判断当前项是否为该数组的最后一项,如果为最后一次遍历,那么我们让程序执行下一步的操作即可
// 使用forEach进行数组遍历处理
let arr = [1, 2, 3, 4, 5, 6, 7];
let arr2 = [];
function pro(item) {
return new Promise((resolve, reject) => {
setTimeout(() => {
arr2.push(item);
console.log(arr2);
resolve();
}, 1000);
});
}
arr.forEach(async (item, index) => {
await pro(item);
console.log('这里是等待每一次循环结束后的操作');
if (index === arr.length - 1) {
console.log('res', arr2);
}
});
$(function() { setTimeout(function () { var mathcodeList = document.querySelectorAll('.htmledit_views img.mathcode'); if (mathcodeList.length > 0) { var testImg = new Image(); testImg.onerror = function () { mathcodeList.forEach(function (item) { $(item).before('\\(' + item.alt + '\\)'); $(item).remove(); }) MathJax.Hub.Queue(["Typeset",MathJax.Hub]); } testImg.src = mathcodeList[0].src; } }, 1000) })