【分享】一篇搞定js异步输出问题-浅分析(好理解)

小试牛刀,来几个简单的异步问题引出易错小知识点

一、Promise

// Question1
 new Promise(resolve => {
     console.log('promise')
   }).then(function() {
     console.log('promise1')
   }).then(function() {
     console.log('promise2')
   })

输出什么(别说我没提醒,仔细看)?

再来两个

//Question2
new Promise(resolve => {
     resolve(1)
   }).then(function(res) {
     console.log('promise1')
   }).then(function(res) {
     console.log(res)
   })

输出什么呢?

//Question3
new Promise(resolve => {
     resolve(1)
   }).then(function(res) {
     console.log('promise1')
        return res
   }).then(function(res) {
     console.log(res)
   })
//Question4
new Promise(resolve => {
     resolve(1)
   }).then(1).then(function(res) {
     console.log(res)
   })

这个输出什么呢?
这个有几个小小知识点,如果您已经知道可以略过

  1. promise有三个状态,只有变为成功状态才会执行.then中的内容
    • pending[待定]初始状态
    • fulfilled[实现]操作成功
    • rejected[被否决]操作失败
  2. .then会接收上一任务的返回值,如果.then中的操作没有意义会被忽略

接下来我们看一下输出

//Question1
 new Promise(resolve => {
     console.log('promise')
   }).then(function() {
     console.log('promise1')
   }).then(function() {
     console.log('promise2')
   })
 //promise
 //没有resolve,不执行后续操作
//Question2
new Promise(resolve => {
    resolve(1)
  }).then(function(res) {
    console.log(res)//接受resolve传过来的1
  }).then(function(res) {
    console.log(res)//上一步没有返回值
  })
//1 undefined
//Question3
new Promise(resolve => {
     resolve(1) //状态成功,往下执行
   }).then(function(res) {
     console.log('promise1')
        return res
   }).then(function(res) {
     console.log(res)//接受上一步的返回值
   })
//promise1 1
//Question4
new Promise(resolve => {
     resolve(1)//成功,往下执行
   }).then(1).then(function(res) {
     console.log(res)//.then(1)被略过,接收resolve(1)传过来的1
   })
//1

其他Promise的知识点就不做赘述了,仅列出几个不容易注意的和我经常犯错的

二、async/await

再来看一个简单的小例子

async  function async1() {
    console.log(1)
    await waitHandle()
    console.log(2)
}
async1()
function waitHandle(){console.log(3)}

这个很简单,输出为1,3,2,但是原理是什么呢,实际上他是这样执行的

function async1(){
    console.log(1)
    return new Promise((res) => {
        console.log(3)
    }).then((res) => {
        //await执行完毕后才会执行后面的操作
        console.log(2)
    })
}

三、setTimeout

//3.1
setTimeout(function () {
    console.log('1');
}, 0);
setTimeout(function () {
    console.log('2');
})
//3.2
setTimeout(function () {
    console.log('1');
}, 10);
setTimeout(function () {
    console.log('2');
})

结论:3.1 1 2

结论:3.2 2 1

与时间有关,谁先执行完谁输出
setTimeout(function () {
    console.log('1');
    new Promise(function (resolve) {
        console.log('2');
        resolve();
    }).then(function () {
        console.log('3')
    })
}, 0);
setTimeout(function () {
    console.log('6');
    new Promise(function (resolve) {
        console.log('7');
        resolve();
    }).then(function () {
        console.log('8');
    });
})
  • 两个宏任务,时间没有差别,按顺序执行
  • 先输出1
  • 遇到微任务,要执行完宏任务中所有微任务才能进入下一块宏任务
    输出 2 3
  • 所有微任务执行完 进入下一个宏任务,一样的步骤 **输出6 7 8 **

上面的再稍稍修改下

setTimeout(function () {
    console.log('1');
    new Promise(function (resolve) {
        console.log('2');
        resolve();
    }).then(function () {
        console.log('3')
        //新增一个setTimeout
        setTimeout(function () {
            console.log('9');
            new Promise(function (resolve) {
                console.log('10');
                resolve();
            }).then(function () {
                console.log('11');
            });
        })
    })
}, 0);
setTimeout(function () {
    console.log('6');
    new Promise(function (resolve) {
        console.log('7');
        resolve();
    }).then(function () {
        console.log('8');
    });
})

区别在于第一个宏任务中又包含一个宏任务,那应该是怎么处理,执行所有的微任务,宏任务当然放到下一轮宏任务去处理啦
图片占位2
所以相比上一个新增的宏任务要接着上一步的解析接着写

  • 两个宏任务,时间没有差别,按顺序...
  • ...务,一样的步骤 **输出6 7 8
  • 输出9 10 11

四、实战开始

前面给像我一样基础不扎实的小伙伴做铺垫用,接下来进入正文,面试中经常考到的执行顺序不懂的时候令人头疼,仔细分析下来会爱上分析的过程

想弄明白执行顺序有几个不得不知的概念(都是重点)

  • 同步任务:众所周知js是单线程,一个个任务排排站一个一个的执行,称之为同步任务,放入主线程队列
  • 异步任务:区别于主线程任务,放在等待队列中执行,等所有主线程任务全部结束后再执行等待队列中的任务,但谁先执行完谁输出(例如settimeout设置时间为0的比设置为100的先输出,尽管为0的是后天加进等待队列的),其余的视为“按顺序”执行
  • 微任务、宏任务:执行等待队列也是有“规则“而言的,先执行微任务,后执行宏任务,两个宏任务要执行完里面的任务后再执行下一个宏任务,如果宏任务里面还有宏任务要把宏任务抛出来放到下一轮执行

常见的微任务:

  • Promise(async/await):Promise并不是完全的同步任务,可以理解为状态完成后.then中的操作要放入等待队列中,await同理

常见的宏任务:

  • 定时器
  • 事件绑定
  • ajax
  • 回调函数
  • Node中fs可以进行异步的I/O操作

结束枯燥的概念时间(虽然枯燥但是一定要看,很有用处,下文会尽量减少文字),我们来小小的检验一下理论的知识带来的成果

//网络经典题1,先做题再对答案再看分析哦~
Promise.resolve().then(() => {
  console.log('promise1');
  const timer2 = setTimeout(() => {
    console.log('timer2')
  }, 0)
});
const timer1 = setTimeout(() => {
  console.log('timer1')
  Promise.resolve().then(() => {
    console.log('promise2')
  })
}, 0)
console.log('start’);

思路:首先先分队列,分出同步队列任务和等待队列任务

  1. 第一步中,Promise直接返回成功状态,.then中的操作为异步块,将其中的操作放入等待队列
  2. setTimeou为异步任务,将里面操作也放入等待队列
主线程任务 等待队列
console.log('start’) console.log('promise1');
-- const timer2 = setTimeout(() => {console.log('timer2')}, 0)
-- console.log('timer1')
-- Promise.resolve().then(() => {console.log('promise2')})

3.接下来先清空主线程任务。输出start

4.等待队列中,按规则输出(先同步,后微任务,最后宏任务)

5.输出promise1 和 timer1

6.微任务优于宏任务 输出promise2

7.最后宏任务 输出timer2

最后结果:start promise1 timer1 promise2 timer2
按照上面的顺序 我们再来试一个

//网络经典题2,先做题再对答案再看分析哦~
async  function async1() {
    console.log(1)
    await async2()
    console.log(2)
}
async function async2() {
    setTimeout(function () {
        console.log(3)
    },99.999)
}
console.log(4)
setTimeout(function () {
    console.log(5)
},100)
async1()
new Promise(function (resolve) {
    console.log(6)
    resolve()
}).then(function () {
    console.log(7)
})
console.log(8);
  • 从上往下看,定义了两个async函数,暂时未调用,不做操作不放入队列
  • 遇到console.log(4),输出 4
  • 遇到setTimeout放入等待队列
  • 调用async1,执行async1内容,遇到console.log(1),输出1
  • 遇到async2,执行async2内容,async2内容是setTimeout放入等待队列
  • await后的内容相当于放入.then中执行所以console.log(2)放入等待队列,这步分析可以参考二、async
  • 继续执行,遇到new Promise先执行里面内容 console.log(6)输出6
  • 将Promise .then中的操作console.log(7)放入等待队列
  • 主线程任务最后一个同步任务 console.log(8) 输出8
  • 主线程任务执行完毕看等待队列中内容,按规则执行,先把同步的任务先输出出来console.log(2) console.log(7) 输出 2 7
  • 再比较省下两个setTimeout,显而易见先输出3后输出5
    最后输出 4 1 6 8 2 7 3 5

是不是有点理解了?就是把任务一步一步分出去,最后按顺序按规则输出,其中的坑已经在前面都总结了一些,接下来自己试一试吧

console.log('1');
setTimeout(function () {
    console.log('2');
    new Promise(function (resolve) {
        console.log('3');
        resolve();
    }).then(function () {
        console.log('4');
    })
}, 0);
new Promise(function (resolve) {
    console.log('5');
    resolve();
}).then(function () {
    console.log('6');
});
setTimeout(function () {
    console.log('7');
    new Promise(function (resolve) {
        console.log('8');
        resolve();
    }).then(function () {
        console.log('9');
    });
})

图片占位

这个问题眼熟么,就是第三部分中的原题啊!!看过透题的小伙伴应该可以答对了吧,答不对的抽自己两巴掌回去再看一下~
最后结果:1 5 6 2 3 4 7 8 9

五、自己试试吧

看完前面如果如果我写的足够通俗并且您也仔细看过了,正确率应该能达到90%,剩下10%可能是有些马虎或者漏掉了需要注意的地方,也可能是我道行浅遇到的问题不够多

//5.1
async function asy2() {
    return new Promise(resolve => {
        setTimeout(function () {
            console.log(1)
        });
        resolve(2)
    }).then(value => {
        console.log(value)
    })
}
async function asy() {
    await asy2();
    console.log(3)
}
asy()
//5.2
async function async1() {
    console.log(1)
    await async2()
    console.log(2)
    return await 3
}
async function async2() {
    console.log(4)
}

setTimeout(function() {
    console.log(5)
}, 0)

async1().then(v => console.log(v))
new Promise(function(resolve) {
    console.log(6)
    resolve();
    console.log(7)
}).then(function() {
    console.log(8)
})
console.log(9)
//5.3
(function() {
    setTimeout(() => {
        console.log(0);
    });
    new Promise(resolve => {
        console.log(1);
        setTimeout(() => {
            resolve();
            Promise.resolve().then(() => {
                console.log(2);
                setTimeout(() => console.log(3));
                Promise.resolve().then(() => console.log(4));
            });
        });
        Promise.resolve().then(() => console.log(5));
    }).then(() => {
        console.log(6);
        Promise.resolve().then(() => console.log(7));
        setTimeout(() => console.log(8));
    });
    console.log(9);
})();

搜集了一些常见的题,大家试一下吧

5.1 =>2 3 1

5.2 => 1 4 6 7 9 2 8 3 5

5.3 => 1 9 5 0 6 2 7 4 8 3

六、结束

本文用于总结从蒙顺序到自己开始分析的学习过程,用词也并不专业,如有错误及补充欢迎大家指出,共同进步,希望本篇文章能帮助到和我曾经一样有疑惑的同学

©著作权归作者所有,转载或内容合作请联系作者
平台声明:文章内容(如有图片或视频亦包括在内)由作者上传并发布,文章内容仅代表作者本人观点,简书系信息发布平台,仅提供信息存储服务。