关于异步 宏任务微任务的理解,以及promise async settimeout执行顺序的问题

在面试的过程中难免会碰到让你说出异步执行顺序的问题,一起来看一下吧。内容持续更新中~

异步和同步的概念

javascript语言是一门“单线程”的语言,可以改变程序正常执行顺序的操作就可以看成是异步操作。
总结:同步阻塞进程,异步不需要等待。


//异步之setTimeout
setTimeout(function () {
            console.log('5');
        }, 10000)
console.log('8');
这里正常理解是先执行5,再执行8,但是我们不可能要等10秒执行完5,再执行8.     
由于这里的settimeout就是异步操作,我们可以先执行8,再执行5

那么系统是怎么理解的呢,在执行代码的时候,浏览器会默认setTimeout以及ajax请求这一类的方法都是耗时程序,默认将其加入一个专门用于存储耗时程序的队列中,只有当所有不耗时的程序都执行完,再来执行这个队列中的程序。

同步就是一件事一件事的执行。只有前一个任务执行完毕,才能执行后一个任务。
异步不需要等前一个任务完成。

js有哪些异步

按照你所期望的顺序执行,返回符合预期的结果
1.定时器
2.事件绑定
3.发布/订阅模式
4.回调函数
5.ajax
6.promise
7.async await

定时器


setTimeout(function(){
console.log('1');

    },3000)
    setTimeout(function(){
        console.log('2');
    },2000) 
console.log('3');//3,2,1

事件绑定

利用事件驱动。这种思路是说异步任务的执行不取决于代码的顺序,而取决于某个事件是否发生。
比如当你点击按钮才会触发事件。

发布/订阅模式(观察者模式)

在发布/订阅模式中,你可以想象存在一个消息中心的地方,你可以在那里“注册一条消息”,那么被注册的这条消息可以被感兴趣的若干人“订阅”,一旦未来这条“消息被发布”,则所有订阅了这条消息的人都会得到提醒。

回调函数

回调函数:将回调函数A作为B函数的参数并且在B函数内部执行;那么就是先执行B,再执行A。
但是有回调地狱的问题
回调地狱指的是回调函数之间的多层嵌套。


//函数1:
function 伸懒腰(x){
    console.log('伸懒腰');
    x(起床)//执行穿衣服的函数
}
//函数2:
function 穿衣服(x){
    console.log('穿衣服');
    x()//执行起床的函数
}
//函数3:
function 起床(){
    console.log('起床');
}
//开始执行函数
伸懒腰(穿衣服);
//执行结果伸懒腰,穿衣服,起床

这里无论怎么调换函数1函数2函数3的顺序,执行结果始终是伸懒腰,穿衣服,起床,始终按照你期望的起床顺序执行。但是当有多个函数的时候,逻辑变得就不清晰,代码难以编写或者难以维护,我们称为回调地狱。

ajax

静态网站和动态网站都是同步的,但同步方式有缺点:页面请求响应式阻塞,影响用户体验
为了解决这个问题,可以通过变通的手段实现页面的局部更新(隐藏帧),由于隐藏帧不方便就有了Ajax

promise

也是按照你所期望的顺序执行,
1、主要用于异步计算
2、可以将异步操作队列化,按照期望的顺序执行,返回符合预期的结果
3、可以在对象之间传递和操作promise,帮助我们处理队列

promise是什么

是ES6中提供了解决异步编程的一套方案,本身是一个对象

promise能做什么

解决回调地狱,异步代码使用同步的方式书写

promise三个状态

Promise的三个状态:pending,resolve,reject

Promise的优缺点

可以将异步操作以同步操作的流程表达出来,避免了层层嵌套的回调函数。此外,Promise对象提供统一的接口,使得控制异步操作更加容易。
无法取消Promise,一旦新建它就会立即执行,无法中途取消。其次,如果不设置回调函数,Promise内部抛出的错误,不会反应到外部。第三,当处于pending状态时,无法得知目前进展到哪一个阶段(刚刚开始还是即将完成)。


var p = new Promise((resolve, reject) => {
 console.log("1");
 resolve("2"); 
});
 
p.then((value) => {
 console.log(value);
}) 

console.log("4");// 1 4 2,而不是124

promise 链式调用


let p = new Promise(step1)
function step1(resolve,reject){
setTimeout(function(){
        var a = 1
        resolve(a)
    },1000)
}
function step2(resolve,reject){

setTimeout(function(){
        var b = 2
        resolve(b)
    },3000)
}
function step3(resolve,reject){
setTimeout(function(){
        var c = 3
        resolve(c)
    },2000)
}

p.then((data)=>{
    console.log(data);
    return new Promise(step3)
}).then(data=>{
    console.log(data);
    return new Promise(step2)
}).then(data=>{
    console.log(data);
})
// 按顺序执行step1,step3,step2,结果即1 3 2

async await

async 相当于promise,await右边的语句等待完成该语句完成再执行之后的语句,await后面的语句相当于then。



async function fn(){//这里生成Promise
  console.log(1)
  await fn1() //开始生成then
  console.log(2)
}
async function fn1(){
  console.log(3)
  await fn2()
  console.log(4)
}
function fn2(){
  console.log(5)
}
fn() 
console.log(6)

首先执行scrpit下的同步任务fn()执行fn(),打印‘1’,
遇到fn1(),打印‘3’,将‘2’放入任务队列中;
遇到fn2(),打印‘5’,将‘4’放入任务队列中
直接打印‘6’;
现在开始处理队列:
等待fn1()全部执行完后,再执行‘2’,
故先打印‘4’,再打印‘2’
顺序为1 3 5 6 4 2 


异步实现原理(事件循环)

通过的事件循环(event loop),理解了event loop机制,就理解了JS的执行机制


console.log(1)
    setTimeout(function(){
    console.log(2)
},0)
console.log(3)

console.log(1) 是同步任务,放入主线程里
setTimeout() 是异步任务,被放入event table, 0秒之后被推入event queue里
console.log(3) 是同步任务,放到主线程里
当 13在控制条被打印后,主线程去event queue(事件队列)里查看是否有可执行的函数,
执行setTimeout里的函数

宏任务和微任务

macro-task(宏任务):包括整体代码script,setTimeout,setInterval
micro-task(微任务):Promise,process.nextTick
在事件循环机制中:
第一轮宏任务执行script, 执行完毕后开始执行所有的微任务
再执行第二轮宏任务settimeout
注意的是:async函数还是基于Promise的一些封装,而Promise是属于微任务的一种;因此会把await async2()后面的所有代码放到Promise的then回调函数中去。

执行顺序总结:

代码从上而下执行,
只要遇到外部代码,为宏任务1,宏任务1是第一轮宏任务,直接执行,
只要遇到setTimeout,宏任务2,宏任务2是第二轮宏任务,要在所有微任务执行完成后再执行宏任务2,放到宏任务队列
只要遇到new promise直接执行,then中代码是微任务放到微任务队列中,
async代码直接执行
await 右侧代码直接执行
await后面的代码相当于then,放到微任务中。

案例


setTimeout(() => {
//执行后 回调一个宏事件
    console.log('内层宏事件3')
}, 0)
console.log('外层宏事件1');

new Promise((resolve) => {
    console.log('外层宏事件2');
    resolve()
}).then(() => {
    console.log('微事件1');
}).then(()=>{
    console.log('微事件2')
})
外层宏事件1
外层宏事件2
微事件1
微事件2
内层宏事件3
• 首先浏览器执行js进入第一个宏任务进入主线程, 遇到 setTimeout  分发到宏任务Event Queue中

• 遇到 console.log() 直接执行 输出 外层宏事件1

• 遇到 Promise, new Promise 直接执行 输出 外层宏事件2

• 执行then 被分发到微任务Event Queue中

•第一轮宏任务执行结束,开始执行微任务 打印 '微事件1' '微事件2'

•第一轮微任务执行完毕,执行第二轮宏事件,打印setTimeout里面内容'内层宏事件3'

setTimeout(function(){
 console.log('定时器开始啦')
 });
 
 new Promise(function(resolve){
     console.log('马上执行for循环啦');
     for(var i = 0; i < 10000; i++){
         i == 99 && resolve();
     }
 }).then(function(){
     console.log('执行then函数啦')
 });

console.log('代码执行结束');
 
首先执行script下的宏任务,遇到setTimeout,将其放到宏任务的【队列】里

遇到 new Promise直接执行,打印"马上执行for循环啦"

遇到then方法,是微任务,将其放到微任务的【队列里】

打印 "代码执行结束"

本轮宏任务执行完毕,查看本轮的微任务,发现有一个then方法里的函数, 打印"执行then函数啦"

到此,本轮的event loop 全部完成。

下一轮的循环里,先执行一个宏任务,发现宏任务的【队列】里有一个setTimeout里的函数,
执行打印"定时器开始啦"

console.time('start');

setTimeout(function() {
  console.log(2);
}, 10);

setImmediate(function() {
  console.log(1);
});

new Promise(function(resolve) {
  console.log(3);
  resolve();
  console.log(4);
}).then(function() {
  console.log(5);
  console.timeEnd('start')
});

console.log(6);

process.nextTick(function() {
  console.log(7);
});

console.log(8);
script(主程序代码)——>process.nextTick——>promise——>setTimeout——>setImmediate
主体部分: 定义promise的构造部分是同步的,因此先输出34 ,
主体部分再输出68(同步情况下,就是严格按照定义的先后顺序)
process.nextTick: 输出7
promise: 这里的promise部分,严格的说其实是promise.then部分,
输出的是5、以及 timeEnd(‘start’)
setImmediate:输出1,依据上面优先级,应该先setTimeout,
但是注意,setTimeout 设置 10ms 延时
setTimeout : 输出2
综合的执行顺序就是: 3——>4——>6——>8——>7——>5——>start: 7.009ms——>1——>2

// 41368275
    /*
    aysnc和promise是同步任务,按顺序执行,只是aysnc返回的是promise对象
    同步是按顺序执行,4 -> async1()-> 6 -> 8
    执行async1(): 4 -> 1 -> async2()也就是3  ->6 -> 8
    执行所有的微任务即then,4 1 3 6 8 2 7
    执行第二轮宏任务即settimeout 4 1 3 6 8 2 7 5

    */
      async function async1() {
            console.log('1');
            await async2();
            console.log('2');
        }
        async function async2() {
            console.log('3');
        }
        console.log('4');
        setTimeout(function () {
            console.log('5');
        }, 0)
        async1();
        new Promise(function (resolve) {
            console.log('6');
            resolve();
        }).then(function () {
            console.log('7');
        });
        console.log('8');
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值