性格严肃的人跳过这一段,皮百万的不用。
最近,在一次正式场合下,遇到了一道检验js相关原理的题目,当时虎躯一震,这不是送分儿咩?不由分说,大笔一挥,写完之后还骄傲的叉了会儿腰,大概是这样事儿的:
(膨胀使我头大)
完事儿之后,似乎略有不妥,但是作为快乐风男,前进的道路上绝不回头,纵使身后洪水滔天。然而缘分就是这么巧,一个非正式场合下,再次相遇,怎么能放过人前显圣的机会(读书人装逼不叫装逼,叫人前显圣)?一顿键盘后,准备再叉会儿腰,然而正确答案让我猝不及防。。。
async function async1() {
console.log(1)
const result = await async2();
console.log(3)
}
async function async2() {
console.log(2);
}
Promise.resolve().then(() => {
console.log(4)
})
setTimeout(() => {
console.log(5)
})
async1();
console.log(6);
请写出打印结果。对于相关原理不太了解的同学,想来是要翻车的;有点了解的同学,指定会是一副激动的心、颤抖的手,插着腰,敲出162345。然而正确答案:
结果是意外的,这波儿腰就先别插了,咱们象征性的分析分析,这是啥原因。
根据这道题的呈现,可以看出,涉及到的基本原理有以下几个方面:
promise原理
async-await原理
同步异步
宏任务微任务原理
每一个方面其实都包含不少的知识,在这里就不一一细讲了,毕竟网上到处都是(不是不想讲,也不是不会讲,只是之前有同学评论我,都9102年了,再写这种基础活该挨骂。我这向来都是从善如流,虚心听取)。所以咱们只讲涉及到的。
首先,同步异步就很常见了,一笔带过,同步,从上到下,从左到右,按顺序执行code;异步,code执行到该行为时,先收集起来,暂不执行,等到执行时机到来,在执行队列里收集到的行为。
然后,宏任务微任务,简单来说,均属异步行为,一般情况下,一个宏任务里面总是先顺序执行同步代码,再顺序执行该宏任务中的微任务(嵌套的话,会更复杂一些),等到都执行完毕,再进入下一个宏任务。啥是宏任务?script标签包含的code、setTimeout、setInterval、setImmediately、I/O等。啥是微任务?promise.then、process.nextTick等。
接着,promise,一个处理异步行为的工具,属于微任务,例题中相关代码为:
Promise.resolve().then(() => {
console.log(4)
});
怎么理解这段代码?Promise.resolve()返回了一个promise对象(也叫thenable对象),并且这个对象立马被resolve。但是由于resolve函数里面的code是一个异步的行为,所以尽管resolve是在then之前执行,但是,里面的异步行为是排在then执行之后才触发。(异步行为=》执行当前promise实例中存放then方法收集到的函数队列,这个队列是一个微任务队列), 然后这个对象的then方法收集了一个回调函数,放在promise实例的微任务回调队列里(then只是收集,并没有执行,是resolve的执行,才触发了微任务异步队列的执行),then会返回一个新的promise实例,但是这个这里不涉及,暂且不表。
最后,async-await,这道题里涉及到两个很关键的概念:
await 只能在 async 函数中使用。await 后面可以跟普通的函数,也可以跟带有then方法的对象,也就是thenable。如果后面跟的是thenable时,await会收集thenable对象的原型对象上的then方法,并给其注入resolve和reject;然后阻塞当前作用域代码的执行,等待注入的resolve开启微任务异步队列的执行。如果后面不是thenable对象的话,直接开启微任务异步队列的执行。(此处感谢@茹挺进大佬的审查和建议)
执行这段代码,理解上述说明:
var o = {};
o.__proto__.then = function(resolve,reject){
resolve(1);
};
(async ()=>{
var r = await o;
console.log(r);
})();
注意:thenable对象中被注入的resolve函数,如果不执行,那么await将一直阻塞,当前作用域里,await后面的代码永远不会执行。
声明了async1,
声明了async2,
Promise.resolve()返回了一个promise对象,并且这个对象立马被resolve 然后这个对象的then方法收集了一个回调函数,放在promise实例的微任务回调队列里。所以此时,当前宏任务队列里的微任务队列里,只有一个promise的队列,里面有一个打印4的回调。
遇到了setTimeout,回调直接被置入下一个宏任务队列。
执行async1,打印1, 然后执行async2,打印2, 但是此时遇到了await,await做了两件事,1.返回了async1的函数,2。阻塞了async2中await后面的函数,先开启当前微任务异步队列的执行。
await返回后,执行后面的同步代码,打印6,此时同步的代码执行完毕。
同步的代码执行完毕后,执行刚才开启的微任务异步队列,打印4,此时await开启的微任务异步队列执行完毕。
await开启的微任务异步队列执行完毕后,解除阻塞,打印3。
当前宏任务打印完毕,执行下一个宏任务,打印5.
await会如我们分析的这样去做么?它会和promise的微任务队列这样配合?
我们直接写一个例子试一下:
async function async1() {
console.log(1)
const result = await async2();
console.log(3)
}
async function async2() {
console.log(2);
return {
then:(res)=>{
console.log(7);
res();
}
}
}
Promise.resolve().then(() => {
console.log(4)
})
setTimeout(() => {
console.log(5)
})
async1();
console.log(6)
根据我们上面讲的原理,结果应该是1,2,6,4,7,3,5。你去打印试试吧,在谷歌里哦,防止翻车~
需要声明的一点是,我不是一个教授者,我只是一个分享者、一个讨论者、一个学习者,有不同的意见或新的想法,提出来,我们一起研究。分享的同时,并不只是被分享者在学习进步,分享者亦是。
知识遍地,拾到了就是你的。
既然有用,不妨点赞,让更多的人了解、学习并提升。
作者本人的博客链接(可以点击阅读原文打开):https://juejin.im/user/57c22b5c128fe1005fc66d2b/posts
同时小伙伴们可以关注一下我的公众号,给我进行投稿~