promise&微任务宏任务问题整理

1. 问:for循环里面有1000次循环,前500次每次都是把data赋值为1,后500次每次都是把data赋值为2,问:dom一共渲染了几次?

答:DOM一次都没有渲染,因为for循环是一个js执行的持续过程。js和DOM渲染是共享一个线程,js执行时DOM不会渲染。

2. 声明一个promise的时候,是同步函数。如:

new Promise((resolve, reject) => {

        console.log('aaa') // 这一步是同步函数,可直接执行

        resolve('bbb')

}).then((res) => {

        console.log(res)

})

3. async和await的细节,以及它们与promise的关系

async和await与promise是相辅相成的关系:

3.1 执行async函数,返回的是一个Promise对象;

3.2 async/await的try... catch...可以捕获promise的catch,使其捕获异常的方式达成统一

3.3 await 后面的代码都是异步回调,即相当于Promise的then,即如果await等待是一个Promise对象,那么它只想等待resolved状态的Promise,后续语句相当于then回调才会执行,如果等来的是rejected状态的Promise,await接不住,就得使用try..catch..,在catch中接住它,然后在catch中进行一定自定义说明;

await后面执行的Promise是一个rejected状态的Promise,使用try..catch..

!(async function() {
    const p4 = Promise.reject('err1'); // rejected 状态
    try {
        const res = await p4;
        console.log(res);
    } catch (ex) {
        console.error(ex); // try...catch相当于Promise的catch
    }
})()

补充:若await后面跟的不是Promise对象,比如字符串、函数、数字,则直接返回该表达式的执行结果。

await后面跟着得是数字:

!(async function() {
    const data1 = await 400; // 若后面不是Promise对象,则直接返回表达式执行结果,这里是400
    console.log('data1', data1);
})()

4. Promise的then和catch的改变状态

then正常返回fulfilled(resolved)的Promise对象,里面有报错则返回rejected的Promise对象

catch正常也返回fulfilled(resolved)的Promise对象,里面有报错则就返回rejected的Promise对象

Promise.reject(reason)返回一个状态为失败的Promise对象,并将给定的失败信息传递给对应的处理方法catch

Promise.resolve(value)返回一个状态为成功的Promise对象,并将成功信息传递给对应方法then

3,4都可参考文章——https://liuchenyang0515.blog.csdn.net/article/details/106628678(Dell老师自己对课程的总结,这位老师应该是刘晨阳老师,不知道字有没有打对,音是一样的)

5. for...of 与for...in / foreach 等其他遍历方式的区别:for...of / for...in 可进行异步遍历

function muti(num) {
    return new Promise(reslove => {
        setTimeout(() => {
            reslove(num * num)
        }, 1000)
    })
}

const num = {1, 2, 3}

// 使用await时,必须在外面包裹async
// 使用forEach来进行循环遍历时,在浏览器等待1秒后,
// 直接打印出1, 4, 9,当我们想要每隔一秒,依次打印出1,4,9时,
// 这种遍历方式就无法实现了,就可以使用下面的for..of..遍历

num.forEach(async (item) => {
    const res = await muti(item)
    console.log(res)
})

// 使用async包裹住await,此时可以使用匿名函数进行包裹,
// 前面加个'!',是为了避免和上面最后一句不写分号导致当成函数的冲突

!(async function() {
  for(let i of num) {
    const res = await muti(i)
    console.log(res)
  }  
})

上图解析:

forEach 在遍历时,等待一秒后,直接把1,4,9全部依次打印出来,1和4之间,4和9之间并无时间间隔,即等待1s后3个结果同时打印。

因为 for...of 内部处理的机制和 forEach 不同,forEach 是直接调用回调函数,for...of 是通过迭代器的方式去遍历。参考:forEach和for of的执行异步顺序问题 - 掘金

那是因为在 forEach 里的 await 并不会阻塞循环,在第一次循环到 await 后,就先放入微任务中,进行下一个循环,然后等到同步的循环结束,来执行微任务时,3次循环的 await 几乎同时结束,瞬间打印出1,4,9;

而for..of / for...in遍历会阻塞当前循环,在当前的 await 执行完后再进行下一次的循环,所以1,4,9打印出来的时候,每个打印都各隔1秒。

6. promise,微任务宏任务小试牛刀1

setTimeout(() => {
    console.log('timeout1')
    Promise.resolve().then(() => {
        console.log('promise1')
    })
    Promise.resolve().then(() => {
        console.log('promise2')
    })
}, 100)
 
setTimeout(() => {
    console.log('timeout2')
    Promise.resolve().then(() => {
        console.log('promise3')
    })
}, 200)

1. 先将两个setTimeout塞到宏任务队列中

2. 当第一个setTimeout时间到了执行时间的时候,首先会打印timeout1,然后在微任务队列中塞入promise1和promise2

3. 当第一个setTimeout执行完毕后,event loop会去微任务队列检查发现有两个promise,会把两个promise按顺序执行完毕

4. 尝试DOM渲染

5. 执行下一个宏任务,两个promise执行完毕后微任务队列中没有任务了,且dom渲染,会去宏任务队列中执行下一个setTimeout2任务

6. 当setTimeout2执行的时候,先打印timeout2,然后在微任务队列中推入了一个promise3

7. 当setTimeout2执行完毕后会去微任务队列检查,发现有个promise3,会将promise3执行

8. 所以依次打印是: timeout1,promise1,promise2,timeout2,promise3

注意:当setTimeout定时时间间隔一致的时候,旧版本的node可能与浏览器端的运行结果不一样

7. promise,微任务宏任务小试牛刀2

async function async1() {
    console.log('async1 start'); // 2
    await async2();
    console.log('async1 end'); // 7
}
 
async function async2() {
    console.log('async2'); // 3
}
 
console.log('script start'); // 1
 
setTimeout(function (){
    console.log('setTimeout'); // 9
}, 0)
 
async1();
 
new Promise(function (resolve) {
    console.log('promise1'); // 4
    resolve();
    console.log("???"); // 5 这一句是我自己加的,目的考察大家是否知道同步代码和微任务,迷惑大家resolve()后面是否还会执行
}).then(function() {
    console.log('promise2'); // 8
})
 
console.log('script end'); // 6

1. 从上到下,先是2个函数定义

2. 再打印script start

3. 看到setTimeout,里面的回调函数放入宏任务队列中等待执行

4. 接着执行async1(),打印async1 start,看到await async2(),执行async2()后打印async2,await后面的语句相当于Promise的then回调函数,所以是微任务,console.log('async1 end')放入微任务队列

5. 执行new Promise,new Promise里面传的函数是同步代码,所以打印promise1,执行resolve(),后续触发的then回调是微任务,放入微任务队列,然后执行同步代码打印???

6. 打印script end,同步代码执行完毕

7. 检查微任务队列,依次打印async1 end promise2 (这里指的是chrome/73+浏览器,后面会说不同)

8. 尝试DOM渲染(如果DOM结构有变化的话)

9. 检查宏任务队列,打印setTimeout

10. 检查微任务队列为空,尝试DOM渲染,检查宏任务队列为空,执行结束

综上:chrome/73+的浏览器结果(我的电脑chrome版本为90.0.4430.72)

 chrome73之前的版本和我目前Safari/14.1.1 (16611.2.7.1.4)版本打印依次为

chrome/73之前的版本,await后面的代码,即console.log(async1 end),都是等微任务执行完后才执行后面的部分,相当于把console.log(async1 end) 放在.then(function(){console.log('promise2')})这个实打实的微任务之后,个人感觉那会还没把await之后的代码当作then微任务的一部分;

chrome/73+以后的版本,如果await后面跟的是一个常量或者async函数,或者是普通函数,都会把后面紧接着的代码正常添加到微任务队列中。

为什么截图中会返回undefined或Promise对象之后才会打印setTimeout,因为前面是同步代码和微任务执行完毕了,JS引擎工作结束,不同的浏览器每次执行都会返回不同的随机值,后面打印的setTimeout是浏览器打印的,相当于一个阶段工作任务结束和下一个阶段任务开始的tag。

以上有参考Dell老师的JS异步的文章:https://liuchenyang0515.blog.csdn.net/article/details/106628678

  • 1
    点赞
  • 3
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值