有道题,得细说

念念不忘,必有回响(性格严肃的人跳过这一段,皮百万的不用)

最近,在一次正式场合下,遇到了一道检验js相关原理的题目,当时虎躯一震,这不是送分儿咩?不禁分说,大笔一挥,写完以后还骄傲的叉了会儿腰,大概是这样事儿的:promise

(膨胀使我头大)bash

完事儿以后,彷佛略有不妥,可是做为快乐风男,前进的道路上毫不回头,纵使身后洪水滔天。 然而缘分就是这么巧,一个非正式场合下,再次相遇,怎么能放过人前显圣的机会(读书人装逼不叫装逼,叫人前显圣)?一顿键盘后,准备再叉会儿腰,然而正确答案让我猝不及防。。。

看看这道小可爱

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。 然而正确答案:异步

结果是意外的,这波儿腰就先别插了,我们象征性的分析分析,这是啥缘由。

分析

根据这道题的呈现,能够看出,涉及到的基本原理有如下几个方面:async

  1. promise原理
  2. async-await原理
  3. 同步异步
  4. 宏任务微任务原理

每个方面其实都包含很多的知识,在这里就不一一细讲了,毕竟网上处处都是(不是不想讲,也不是不会讲,只是以前有同窗评论我,都9102年了,再写这种基础活该挨骂。我这向来都是从善如流,虚心听取)。因此我们只讲涉及到的。函数

首先,同步异步就很常见了,一笔带过,同步,从上到下,从左到右,按顺序执行code;异步,code执行到该行为时,先收集起来,暂不执行,等到执行时机到来,在执行队列里收集到的行为。工具

而后,宏任务微任务,简单来讲,均属异步行为,通常状况下,一个宏任务里面老是先顺序执行同步代码,再顺序执行该宏任务中的微任务(嵌套的话,会更复杂一些),等到都执行完毕,再进入下一个宏任务。啥是宏任务?script标签包含的code、setTimeout、setInterval、setImmediately、I/O等。啥是微任务?promise.then、process.nextTick等。学习

接着,promise,一个处理异步行为的工具,属于微任务,例题中相关代码为:ui

Promise.resolve().then(() => {
    console.log(4)
});
复制代码

怎么理解这段代码? Promise.resolve()返回了一个promise对象(也叫thenable对象),而且这个对象立马被resolve。 可是因为resolve函数里面的code是一个异步的行为,因此尽管resolve是在then以前执行,可是,里面的异步行为是排在then执行以后才触发。(异步行为=》执行当前promise实例中存放then方法收集到的函数队列,这个队列是一个微任务队列), 而后这个对象的then方法收集了一个回调函数,放在promise实例的微任务回调队列里(then只是收集,并无执行,是resolve的执行,才触发了微任务异步队列的执行),then会返回一个新的promise实例,可是这个这里不涉及,暂且不表。spa

最后,async-await,这道题里涉及到两个很关键的概念:code

2. 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后面的代码永远不会执行。

用分析结果执行代码

  1. 声明了async1,
  2. 声明了async2,
  3. Promise.resolve()返回了一个promise对象,而且这个对象立马被resolve 而后这个这个对象的then方法收集了一个回调函数,放在promise实例的微任务回调队列里。 因此此时,当前宏任务队列里的微任务队列里,只有一个promise的队列,里面有一个打印4的回调。
  4. 遇到了setTimeout,回调直接被置入下一个宏任务队列。
  5. 执行async1,打印1, 而后执行async2,打印2, 可是此时遇到了await,await作了两件事,1.返回了async1的函数,2。阻塞了async2中await后面的函数,先开启当前微任务异步队列的执行。
  6. await返回后,执行后面的同步代码,打印6,此时同步的代码执行完毕。
  7. 同步的代码执行完毕后,执行刚才开启的微任务异步队列,打印4,此时await开启的微任务异步队列执行完毕。
  8. await开启的微任务异步队列执行完毕后,接触阻塞,打印3。
  9. 当前宏任务打印完毕,执行下一个宏任务,打印5.

对await的怀疑

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。 你去打印试试吧,在谷歌里哦,防止翻车~

写在最后

须要声明的一点是,我不是一个教授者,我只是一个分享者、一个讨论者、一个学习者,有不一样的意见或新的想法,提出来,咱们一块儿研究。分享的同时,并不仅是被分享者在学习进步,分享者亦是。

知识遍地,拾到了就是你的。

既然有用,不妨点赞,让更多的人了解、学习并提高。

相关文章
相关标签/搜索