我踩了async和await在JS循环中的坑

304 阅读2分钟

背景介绍

业务场景:后台有12个数据,前端有6个展示框,接口是传递一个参数,异步请求一个数据,但是这12个数据不是每个都有用,所以我们需要过滤掉无用数据,再展示给用户,提高用户体验(不能让用户走到这里渲染太慢),最后我才用的方式是:通过循环发起6个请求,然后将6个请求放到Promise.all中,当内容请求完成后,再对内容进行检测,有不合格就通过while循环发起一个请求,直到有合格数据为止,最后得到6个正确的数据再渲染到界面,在这期间我就踩了循环与async和await的坑;

整一个示例来模拟

整一个学生成绩表,统计不同成绩的人数,我们通过setTimeout来模拟在服务器上请求数据

let StudentGrade = {
    "A": 5,
    "B": 15,
    "C": 8,
    "D": 2,
};
const sleep = (time) => {
    return new Promise((resolve) => {
        setTimeout(resolve, time);
    })
}
const getStudentGradeNum = (grade) => {//模拟服务器上发起请求
    return sleep(1000).then(v=>StudentGrade[grade]);
}
console.log("grade",getStudentGradeNum("A").then(num=>{console.log(num)}));

最后总的统计一下

const result = async () => {
    console.log("start");
    let numA = await getStudentGradeNum("A");
    console.log("A",numA);
    let numB = await getStudentGradeNum("B");
    console.log("B",numB);
    let numC = await getStudentGradeNum("C");
    console.log("C",numC);
    let numD = await getStudentGradeNum("D");
    console.log("D",numD);
    console.log("end");
}
console.log("result",result());

在for循环中使用

let grade = ["A", "B", "C", "D"];

let numLoop = async () => {
   for (let i = 0; i < grade.length; i++) {
        const num = await getStudentGradeNum(grade[i]);
        console.log("num",num);
    } 
}
console.log("numLoop",numLoop());

for循环.gif
所以在for循环中await按照顺序执行,每1秒钟输出一个

在map中使用

let grade = ["A", "B", "C", "D"];

let numLoop = async () => {
    console.log("start");
    
    let nums = await grade.map(async item => {
        const num = await getStudentGradeNum(item);
        return num;
    })
    console.log("nums",nums);
    
    console.log("end");
}
console.log("numLoop",numLoop());

如果你在 map 中使用 awaitmap 返回promise对象,你必须等待promise 的数组得到处理。 或者通过await Promise.all(nums)来完成此操作。
image.png
如下图:通过Promise.all输出结果就不同
map.gif

在forEach循环中使用

let grade = ["A", "B", "C", "D"];

let numLoop = async () => {
    console.log("start");
     grade.forEach(async item => {
        const num = await getStudentGradeNum(item);
        console.log("num",num);
    });
    console.log("end");
}
console.log("numLoop",numLoop());

forEach循环.gif
可以看到先输出了start和end,之后才输出了promise的东西,forEach只支持同步代码,所以不要在这里面使用async和await。

在filter循环中使用

let grade = ["A", "B", "C", "D"];

let numLoop = async () => {
    console.log("start");
    
  let nums = await grade.filter(async item => {
        const num = await getStudentGradeNum(item);
        return num > 5;
  })
  console.log("nums", nums);

    console.log("end");
}
console.log("numLoop",numLoop());

实际上,在filter中使用await根本没有用,因为回调重视返回一个promise,相当于true,所以如果想要正确使用filter是需要其他的方式来配合得
image.png

总结一下

  • 1、想要连续执行await就用for循环
  • 2、不要在forEach中使用await,forEach只支持同步代码
  • 3、在map中使用await时,可以考虑使用Promise.all来处理promise对象
  • 4、在filter中使用await时,可以先用map处理后,再使用filter