7个demo彻底搞定Event Loop面试题

174 阅读6分钟

以下7个demo 如果可以彻底掌握Event Loop面试题,基本上你能遇到的面试题都可以拿下

demo1、await 与微任务

难点:await 后的代码先执行还是外面同步 代码先执行

console.log('aaa');

(async ()=>{
  console.log('bbb');
  await console.log('ccc');
  console.log('eee')
})().then(()=>{
  console.log('fff')
});

console.log('ddd');

难点:

1、asyc 中 await 之前算同步还是异步?

2、await 后如果是同步代码 算同步还是异步

3、await 后跟的是同步代码,那 await 还会生效吗

一个结论:await其实等价于then(事实上他俩也确实是一个东西),都是将后续任务放到微任务队列中等待,而不会立即执行

  • 第1步、aaa不说了
  • 第2步、111是同步执行的,上面说过
  • 第3步、222这里很重要了,首先,console.log自己是同步的,所以立即就会执行,我们能直接看到222,但是await本身就是then,所以console.log(333)无法直接执行,而是老老实实去排队,而且,因为整个async并未执行完,它的then(也就是444)无法触发
  • 第4步、ddd应该也不用说,当前任务到这里执行完毕
  • 第5步、从任务队列中把333拉出来,并且执行了,这时整个async才算完成,所以把then推到队列中等待执行
  • 第6步、把console.log(444)拉出来执行,看到444

总结

  • Promise构造函数中的代码属于同步,包含 await 后跟的同步代码
  • then、catch中的代码才属于 微任务

demo2、宏任务与微任务

难点:微任务内的宏任务先执行还是外面宏任务先执行

console.log('aaa');

setTimeout(()=>console.log('t1'), 0);
(async ()=>{
  console.log(111);
  await console.log(222);
  console.log(333);

  setTimeout(()=>console.log('t2'), 0);
})().then(()=>{
  console.log(444);
});

console.log('bbb');
  • 第1步、毫无悬念aaa,过
  • 第2步、t1会放入任务队列等待
  • 第3步、111会直接执行,因为async本身不是异步的(上面有说)
  • 第4步、222也会直接执行,但是接下来的console.log(333);和setTimeout(()=>console.log('t2'), 0);就塞到微任务队列里等待了
  • 第5步、bbb毫无疑问,而且当前任务完成,优先执行微任务队列,也就是console.log(333)开始的那里
  • 第6步、执行333,然后定时器t2会加入任务队列等待(此时的任务队列里有t1和t2两个了),并且async完成,所以console.log(444)进入微任务队列等待
  • 第7步、优先执行微任务,也就是444,此时所有微任务都完成了
  • 第8步、执行剩下的普通任务队列,这时t1和t2才会出来

总结

  • 1、先将当前宏任务内的同步代码执行完
  • 2、然后执行当前宏任务内的微任务
    • 遇到宏任务直接丢入宏任务队列
  • 3、执行下一个宏任务

demo3 Promise 内嵌 Promise

难点:双层内嵌 Promise 第二个 then 的执行顺序?

setTimeout(() => {
  console.log('0');
}, 0)

new Promise((resolve, reject) => {
  console.log('1');  
  resolve();
}).then(() => {     // w1
  console.log('2');    
  
  // return
  new Promise((resolve, reject) => {
    console.log('3');
    resolve();   // w2
  }).then(() => {     // 📌
    console.log('4');
  }).then(() => {   // w4
    console.log('5');
  })
}).then(() => {
  console.log('6');   //w3 📌
})

new Promise((resolve, reject) => {
  console.log('7');
  resolve()
}).then(() => {        
  console.log('8');
})
//[ w1,w2,w3,w4]
//注释为📌的两个then是同层级的,所以按照执行顺序来打印
  • 同步代码中遇到 then 则将 then 中的代码(包含 then 后的 then 和 catch)加入微任务队列,后面遇到微任务继续加入队列
    • 同理宏任务嵌套宏任务也是如此
  • 直到当前微(宏)任务执行完

demo4

难点:多次resolve 以哪个为准

new Promise((resolve, reject) => {
    console.log('promise');

    for (var i = 0; i < 10000; ++i) {
        i === 9999 && resolve(i);
    }

    console.log('promise after for-loop');
}).then((v) => {
    console.log('promise1 i:'+v);
}).then(() => {
    console.log('promise2');

});

// promise1 i:  会是几?
console.log('start');

var intervalA = setInterval(() => {
    console.log('intervalA');
}, 0);

setTimeout(() => {
    console.log('timeout');

    clearInterval(intervalA);
}, 0);

var intervalB = setInterval(() => {
    console.log('intervalB');
}, 0);

var intervalC = setInterval(() => {
    console.log('intervalC');
}, 0);

new Promise((resolve, reject) => {
    console.log('promise');

    for (var i = 0; i < 10000; ++i) {
        i === 9999 && resolve();
    }

    console.log('promise after for-loop');
}).then(() => {
    console.log('promise1');
}).then(() => {
    console.log('promise2');

    clearInterval(intervalB);
});

new Promise((resolve, reject) => {
    setTimeout(() => {
        console.log('promise in timeout');
        resolve();
    });

    console.log('promise after timeout');
}).then(() => {
    console.log('promise4');
}).then(() => {
    console.log('promise5');

    clearInterval(intervalC);
});

Promise.resolve().then(() => {
    console.log('promise3');
});

console.log('end');
  1. clearInterval(intervalA); 运行的时候,实际上已经执行了 intervalA 的macrotask了\
  2. promise函数内部是同步处理的,不会放到队列中,放入队列中的是它的then或catch回调\
  3. promise的then返回的还是promise,所以在输出promise4后,继续检测到后续的then方法,马上放到microtask队列尾部,再继续取出执行,马上输出promise5;
  • 提前clearInterval 并不会销毁宏任务,因为当前宏任务已经加入 宏任务队列等待执行

demo5

难点:多个script 内的微任务宏任务 执行顺序??

    <script>
        console.log('start');

        setTimeout(() => {
            console.log('timeout1');
        }, 0);

        Promise.resolve().then(() => {
            console.log('promise1');
        });
    </script>
    <script>
        setTimeout(() => {
            console.log('timeout2');
        }, 0);

        requestAnimationFrame(() => {
            console.log('requestAnimationFrame');
        });

        Promise.resolve().then(() => {
            console.log('promise2');
        });

        console.log('end');
    </script>

demo6 new Promsie 后的throw问题

new Promise((res, rej) => {
  console.log(1);
  throw new Error('abc');  //抛出错误,下面的代码不执行
  res(2);
  console.log(3);
}).catch((e) => {

  console.log('catch');

}).then((t) => {

  console.log(t);

});

变形

new Promise((res, rej) => {

  console.log(1);
  
  res(2);   //状态发生转变后,后续的错误也无法捕获
  
  throw new Error('abc');

  console.log(3);


}).catch((e) => {

  console.log('catch');

}).then((t) => {

  console.log(t);

});
// 输出 1 --> 2

demo7 new Promise中的await问题

//快手一面
let a;
const b = new Promise((resolve, reject) => {
  console.log('promise1');
  resolve();
}).then(() => {
  console.log('promise2');
}).then(() => {
  console.log('promise3');
}).then(() => {
  console.log('promise4');
});

a = new Promise(async (resolve, reject) => {
  console.log(a);
  await b;
  console.log(a); 
  console.log('after1');
  await a  // 后面的代码属于新的微任务,所以,后面非同步代码 且a第一步构造函数内未经历resolve。故a有值状态为pending  
  resolve(true);
  console.log('after2');
}).then(data => {console.info(data)})

console.log('end');

其实以上难点集中在

let a
a = new Promise(async ()=>{
    console.log(0)
    await a;
    console.log(1)
    await a;
    console.log(2)
})

为什么console.log(2)不会执行呢

首先第一步 await a; 执行时当成同步代码,此时a为undefined,await a; 之后的代码为下一次微任务,此时同步代码执行完毕开始执行 第一个await a 后面的下一次微任务代码 , 第二个 await a;之前无 resolve 所以,第二个await a; 即a为pending状态的Promise,其之后的代码一直无法执行

原题可拆解为

let a;
const b = new Promise((resolve, reject) => {
  console.log('promise1');
  resolve();
}).then(() => {
  console.log('promise2');
}).then(() => {
  console.log('promise3');
}).then(() => {
  console.log('promise4');
});

a = new Promise(async (resolve, reject) => {
  console.log(a);
  b.then(() => {
  console.log(a); 
  console.log('after1');
  a.then((resolve)=>{  // a中没有resolve处理所有  a后面的都处于pending状态无法执行
        resolve(true);
        console.log('after2');
  })

})
 
}).then(data => {console.info(data)})

console.log('end');

以下是一个最简单的demo加深理解

let a = new Promise(()=>{})
new Promise(async (resolve, reject) => {
  console.log(0);
  resolve(1)
  console.log(2);

}).then(()=>{

    console.log(3)
})
// 输出 1,2,3
let a = new Promise(()=>{})
new Promise(async (resolve, reject) => {
  console.log(0);
  await a
  resolve(1)
  console.log(2);

}).then(()=>{

    console.log(3)
})
// 输出 0

为何1,2不会执行,将上面改造如下即可,观察代码可知第二个then 无resolve 和reject故始终保持pending 其后面的then代码始终无法执行

let a = new Promise(()=>{})
new Promise(async (resolve, reject) => {
  console.log(0);
}).then(()=>{
   console.log(3)
}).then(()=>{
}).then((resolve)=>{
  resolve(1)
  console.log(2);
}).then(()=>{
    console.log(3)
})
// 输出 0

知识点

  • 无论是写new Promise 还是直接const a= new Promise ;都会立即执行,
  • const a= new Promise 内部 取不到当前值
  • 构造函数中无 resolve 则后面的then 不会执行
  • 没有 resolve reject 则 不会往下执行
  • Promise构造函数内遇到await时,await后的代码看出then中的代码,如果 await之前没有resolve 则该构造函数始终保持pending 而无法走到自己的then状态

参考 zhuanlan.zhihu.com/p/46068171 www.cnblogs.com/thatme/p/10… juejin.cn/post/697381… www.ruanyifeng.com/blog/2013/1… eslint.org/docs/rules/… juejin.cn/post/685041…