promise和eventloopm面试题(学习笔记)

    科技2022-07-13  110

    版本一:得心应手版 考点:eventloop中的执行顺序,宏任务微任务的区别。

    setTimeout(()=>{ console.log(1) },0) Promise.resolve().then(()=>{ console.log(2) }) console.log(3) //3 2 1

    了解宏任务(marcotask)微任务(microtask) 这个是属于Eventloop的问题。main script运行结束后,会有微任务队列和宏任务队列。微任务先执行,之后是宏任务。 PS:概念问题 有时候会有版本是宏任务>微任务>宏任务, 这里有个main script的概念,就是一开始执行的代码(代码总要有开始执行的时候对吧,不然宏任务和微任务的队列哪里来的),这里被定义为了宏任务(笔者喜欢将main script的概念单独拎出来,不和两个任务队列混在一起),然后根据main script中产生的微任务队列和宏任务队列,分别清空,这个时候是先清空微任务的队列,再去清空宏任务的队列。 版本二:游刃有余版 考点:Promise的executor以及then的执行方式

    setTimeout(()=>{ console.log(1) },0) let a=new Promise((resolve)=>{ console.log(2) resolve() }).then(()=>{ console.log(3) }).then(()=>{ console.log(4) }) console.log(5) //2 5 3 4 1

    拓展思考: 为什么有时候,then中的函数是一个数组?有时候就是一个函数? 我们稍稍修改一下上述题目,将链式调用的函数,变成下方的,分别调用then。且不说这和链式调用之间的不同用法,这边只从实践角度辨别两者的不同。链式调用是每次都生成一个新的Promise,也就是说每个then中回调方法属于一个microtask,而这种分别调用,会将then中的回调函数push到一个数组之中,然后批量执行。再换句话说,链式调用可能会被Evenloop中其他的函数插队,而分别调用则不会(仅针对最普通的情况,then中无其他异步操作。)。

    let a=new Promise((resolve)=>{ console.log(2) resolve() }) a.then(()=>{ console.log(3) }) a.then(()=>{ console.log(4) })

    下一模块会对此微任务(microtask)中的“插队”行为进行详解。 版本三:炉火纯青版 如果在promise的then中创建一个promise,那么结果该如何呢? 考点:promise的进阶用法,对于then中return一个promise的掌握

    new Promise((resolve,reject)=>{ console.log("promise1") resolve() }).then(()=>{ console.log("then11") new Promise((resolve,reject)=>{ console.log("promise2") resolve() }).then(()=>{ console.log("then21") }).then(()=>{ console.log("then23") }) }).then(()=>{ console.log("then12") })

    按照上一节最后一个microtask的实现过程,也就是说一个Promise所有的then的回调函数是在一个microtask函数中执行的,但是每一个回调函数的执行,又按照情况分为立即执行,微任务(microtask)和宏任务(macrotask)。 第一轮current task: promise1是当之无愧的立即执行的一个函数,参考上一章节的executor,立即执行输出[promise1] micro task queue: [promise1的第一个then] 第二轮current task: then1执行中,立即输出了then11以及新promise2的promise2 micro task queue: [新promise2的then函数,以及promise1的第二个then函数] 第三轮current task: 新promise2的then函数输出then21和promise1的第二个then函数输出then12。 micro task queue: [新promise2的第二then函数] 第四轮current task: 新promise2的第二then函数输出then23 micro task queue: [] 最终结果[promise1,then11,promise2,then21,then12,then23]。

    变异版本1:如果说这边的Promise中then返回一个Promise呢??

    new Promise((resolve,reject)=>{ console.log("promise1") resolve() }).then(()=>{ console.log("then11") return new Promise((resolve,reject)=>{ console.log("promise2") resolve() }).then(()=>{ console.log("then21") }).then(()=>{ console.log("then23") }) }).then(()=>{ console.log("then12") })

    promise1 then11 promise2 then21 then23 then12 这里就是Promise中的then返回一个promise的状况了,这个考的重点在于Promise而非Eventloop了。这里就很好理解为何then12会在then23之后执行,这里Promise的第二个then相当于是挂在新Promise的最后一个then的返回值上。 变异版本2:如果说这边不止一个Promise呢,再加一个new Promise是否会影响结果?

    new Promise((resolve,reject)=>{ console.log("promise1") resolve() }).then(()=>{ console.log("then11") new Promise((resolve,reject)=>{ console.log("promise2") resolve() }).then(()=>{ console.log("then21") }).then(()=>{ console.log("then23") }) }).then(()=>{ console.log("then12") }) new Promise((resolve,reject)=>{ console.log("promise3") resolve() }).then(()=>{ console.log("then31") })

    第一轮current task: promise1,promise3 micro task queue: [promise1的第一个then,promise3的第一个then] 第二轮current task: then11,promise2,then31 micro task queue: [promise2的第一个then,promise1的第二个then] 第三轮current task: then21,then12 micro task queue: [promise2的第二个then] 第四轮current task: then23 micro task queue: [] 最终输出:[promise1,promise3,then11,promise2,then31,then21,then12,then23] 版本四:登峰造极版 考点:在async/await之下,对Eventloop的影响。

    async function async1() { console.log("async1 start"); await async2(); console.log("async1 end"); } async function async2() { console.log( 'async2'); } console.log("script start"); setTimeout(function () { console.log("settimeout"); },0); async1(); new Promise(function (resolve) { console.log("promise1"); resolve(); }).then(function () { console.log("promise2"); }); console.log('script end');

    async/await仅仅影响的是函数内的执行,而不会影响到函数体外的执行顺序。也就是说async1()并不会阻塞后续程序的执行,await async2()相当于一个Promise,console.log(“async1 end”);相当于前方Promise的then之后执行的函数。 按照上章节的解法,最终输出结果:[script start,async1 start,async2,promise1,script end,async1 end,promise2,settimeout] 如果了解async/await的用法,则并不会觉得这题是困难的,但若是不了解或者一知半解,那么这题就是灾难啊。 此处唯一有争议的就是async的then和promise的then的优先级的问题,请看下方详解。 async/await与promise的优先级详解 async/await有时候会推迟两轮microtask,在第三轮microtask执行,主要原因是浏览器对于此方法的一个解析,由于为了解析一个await,要额外创建两个promise,因此消耗很大。后来V8为了降低损耗,所以剔除了一个Promise,并且减少了2轮microtask,所以现在最新版本的应该是“零成本”的一个异步。 版本五:究极变态版 考点:nodejs事件+Promise+async/await+佛系setImmediate

    async function async1() { console.log("async1 start"); await async2(); console.log("async1 end"); } async function async2() { console.log( 'async2'); } console.log("script start"); setTimeout(function () { console.log("settimeout"); }); async1() new Promise(function (resolve) { console.log("promise1"); resolve(); }).then(function () {console.log("promise2");}); setImmediate(()=>{console.log("setImmediate")}) process.nextTick(()=>{console.log("process")}) console.log('script end');

    第一轮:current task:“script start”,“async1 start”,‘async2’,“promise1”,“script end” micro task queue:[async, promise.then, process] macro task queue:[setTimeout, setImmediate] 第二轮current task:process,async1 end ,promise.then micro task queue:[] macro task queue:[setTimeout,setImmediate] 第三轮current task:setTimeout,setImmediate micro task queue:[] macro task queue:[] 最终结果:[script start,async1 start,async2,promise1,script end,process,async1 end,promise2,setTimeout,setImmediate] 同样"async1 end","promise2"之间的优先级,因平台而异。 在处理一段evenloop执行顺序的时候: 第一步确认宏任务,微任务 宏任务:script,setTimeout,setImmediate,promise中的executor 微任务:promise.then,process.nextTick 第二步解析“拦路虎”,出现async/await不要慌,他们只在标记的函数中能够作威作福,出了这个函数还是跟着大部队的潮流。 第三步,根据Promise中then使用方式的不同做出不同的判断,是链式还是分别调用。 比如,process.nextTick优先级高于Promise.then

    Processed: 0.009, SQL: 8