一道样题造成的知识盲点//promise1newPromise(resolve=>{resolve(newPromise(resolve=>{resolve(1);}));})。然后(res=>{console.log('1');});newPromise(resolve=>{resolve(2);}).then(()=>{console.log('2');}).then(()=>{console.log('3');}).then(()=>{console.log('4');});//输出顺序://2//3//1//4让我们先来看一个示例问题。按照之前的理解,我以为输出顺序是2134。后来通过调试,发现初始化后promise1的状态还是pending,感觉自己对Promise微任务的理解还是不够。在研究了ECMA规范之后,我终于搞清楚了这个问题。微任务的类型ECMA规范将Promise微任务分为两种类型。下面结合规范看一下这两类微任务的生成时机和执行内容。NewPromiseReactionJob会在Promise解析后执行then()中注册的回调时生成此微任务,或者在注册then()时Promise解析时生成此微任务。为了简化描述,我们先关注这个场景:当前Promise已经resolved,然后调用then(),例如:Promise.resolve(1).then(res=>console.log(res));res=>控制台。log(res)在这样的微任务中运行。我们看规范对Promise.prototype.then的描述:既然Promise已经是fulfilled状态,那么我们看一下PerformPromiseThen中fulfilled状态的操作:这里是创建一个NewPromiseReactionJob微任务,加入到微任务队列中。我们来看看NewPromiseReactionJob是如何执行的:这个microtask主要包括两个内容:executehandler,handler是在then()中注册的回调,获取返回结果。resolve(返回结果)或reject(返回结果)then()中生成的新Promise。NewPromiseResolveThenableJob上面的微任务基本上是众所周知的情况。这种microtask就是例题中提到的盲区。首先注意resolve函数的描述:如果一个对象的then属性可以被调用(是一个函数),那么这个对象就是一个thenable对象。如果调用resolve()传递的参数值是一个thenable对象,就会产生一个类似NewPromiseResolveThenableJob的微任务。接下来看这个microtask的内容:大概意思是这个microtask产生了如下代码:thenable.then(解决,拒绝);然后结合第一个microtask,如果thenable对象是Promise,那么这个microtask执行完之后就会产生第一个microtask。你为什么要这样做?规范中有这样的解释:ThisJobusesthesuppliedthenableanditsthenmethodtoresolvethegivenpromise。此过程必须作为作业进行,以确保then方法的评估发生在任何周围代码的评估完成之后。Directly翻译的话,大概意思就是这个要等到周围的同步代码执行完才会执行。对于这个设计意图,我的理解是thenable对象不一定是Promise实例,可以是用户创建的任意对象;如果这个对象的then是一个同步方法,那么这样可以保证then的执行顺序也在微任务中。例题分析再分析一下例题://promise1newPromise(resolve=>{resolve(//promise2newPromise(resolve=>{resolve(1);}));}).then(res=>{console.log('1');});//promise3newPromise(resolve=>{resolve(2);}).then(()=>{console.log('2');}).then(()=>{console.log('3');}).then(()=>{console.log('4');});代码执行后,我们用伪代码表示微任务队列内容:constmicroTasks=[functionjob1(){promise2.then(promise1.[[Resolve]],promise1.[[Reject]]);},functionjob2(){consthandler=()=>{console.log('2');};//解析then()返回的新Promise。解决(处理程序(2));}];然后开始执行微任务队列。job1执行完后,会生成一个新的microtaskjob3:constmicroTasks=[functionjob2(){consthandler=()=>{console.log('2');};解决(处理程序(2));},functionjob3(){consthandler=promise1.[[Resolve]];解决(处理程序(1));}];job2执行完后输出2,生成新的microtaskjob4:constmicroTasks=[functionjob3(){consthandler=promise1.[[Resolve]];解决(处理程序(1));},functionjob4(){consthandler=()=>{console.日志('3');};解决(处理程序(未定义));}];注意job3的内容会让promise1resolve,然后执行promise1的then回调,生成一个microtaskjob5;并且job4执行后会输出改为23,让then()生成的新Promiseresolution也生成下一个microtaskjob6:constmicroTasks=[//job5由job3生成functionjob5(){consthandler=()=>{console.log('1');};解决(处理程序(1));},functionjob6(){consthandler=()=>{console.log('4');};解决(处理程序(未定义));}];那么最后的输出就是2314,大家可以把上面的分析方法放到其他题中去验证康康是否正确。我的JS博客:小生比比JavaScript参考ECMAScript?2023语言规范-Promise对象
