想要详细学习的背景:
遇到了一个bug,按照正常js单线程的逻辑,就是执行顺序是:A-->B-->C,此时应该是获取到了round的,但是事实是并没有获取到。而代码的执行顺序如下图所示:A-->C-->B。
1、javascript
js作为浏览器脚本语言,主要是与用户交互和操作DOM,为了避免操作DOM出现要获取DOM的时候出现了删除DOM的尴尬操作,所以js就作为一个单线程语言出现了,js的多线程也是由单线程模拟出来的:是为了利用多核CPU的计算能力,HTML5提出Web Worker标准,允许JavaScript脚本创建多个线程,但是子线程是由主线程控制的,而且不允许操作DOM。
1.1 js的同步任务和异步任务
单线程就表示这个完成了,才会进行下一个。如果其中一个任务执行很久,下一个任务就得一直等着,这样用户体验很差的同时还浪费了CPU,并且IO设备需要时间(比如ajax请求是需要时间拿到数据的),所以js的设计者发现主线程完全可以不管IO设备,挂起处于等待中的任务,先运行排在后面的任务。等到IO设备返回了结果,再回过头,把挂起的任务继续执行下去。于是就产生了同步任务和异步任务。
同步任务:在主线程上排队执行的任务,只有前一个任务执行完毕,才能执行后一个任务;
异步任务:不进入主线程、而进入"任务队列"(task queue)的任务,只有当主线程中的同步任务为空后,"任务队列"通知主线程,某个异步任务可以执行了,该任务才会进入主线程执行。而异步任务又分为宏任务和微任务。
2、执行机制
但是只以同步任务和异步任务去看一段代码的执行顺序是不够完善的,更准确的应该是以微任务和宏任务的角度去看。
所以这段代码的执行顺序应该是:A--->C-→B,所以没有获取到round属性。
3、promise和async/await
promise 和 async/await都是微任务,实现了延迟执行。 并在每个任务结束时执行,在执行返回到时间循环之前,微任务队列总是被清空。promise对象代表一个异步操作,有三种状态:pending(进行中)、fulfulled(已成功)、rejected(已失败)。promise的状态一旦改变就不会再变,并且不会受到其他操作的影响。
promise实例是具有then、catch、finally方法的,是定义在原型对象Promise.prototype上的,是为promise实例添加状态改变时的回调函数。
then方法是有两个参数可选的,第一个参数是resolved状态的回调函数,第二个参数是rejected状态的回调函数,都是可选的。
catch方法是用于指定错误发生时的回调函数,当promise对象返回的状态为rejected时会调用catch方法指定的回调函数,而then方法指定的回调函数运行时抛出错误的话,也会被catch方法捕获。所以不建议在then方法中定义reject状态的回调函数。
then方法和catch方法都会返回一个promise对象,所以后面还可以继续跟then方法和catch方法。
finally方法是不管promise对象最后的状态如何都会执行的操作。
Promise.all()用于将多个Promise实例包装成一个新的promise实例,接受一个数组作为参数,数组中的元素都是promsie实例,如果不是promise实例也会自动转为promise实例。参数可以不是数组,但必须具有Iterator接口,且返回的每个成员都是Promise实例。只有当p1、p2、p3的状态都变成fulfilled时,p的状态才是fulfilled。一旦其中一个变成rejected,p的状态就是rejected。
Promise.allSettled()方法与Promise.all()方法类似,但是Promise.allSettled()的状态不受参数的影响,会全部执行,并且结果会显示对应Promise对象的执行状态。并且Promise.allSettled()的状态总是fulfilled。
Promise还有很多其他的方法:Promise.any()、promise.resolve()、Promise.race()等。
Async/await :
async函数可以看作是多个异步操作包装成的Promise对象,Await命令就是内部then命令的语法糖。async函数的返回值是promise对象,可以用.then来指定下一步的操作。
await必须要与async函数内使用,但是async函数内可以没有await。
await后面的表达式需要是一个promise对象,如果不是promise会自动转成promise。await语句的执行顺序是从右向左的开始的,当函数执行的时候一旦遇到await就会先返回,等到异步操作完成再接着执行函数体内后面的语句。
await这个语法糖在引擎内的执行代码如图所示:其实本质上就是promise。
对于任何一个await语句后面的promise对象编程reject状态,那么整个async函数都会中断执行。所以为了希望就算await报错了,下面的代码依旧执行,可以将代码放在try{}catch{},这样不管这个异步操作是否成功,都不会造成代码中断。或者在await后面的promise对象后面添加.catch进行错误处理。
关于v8引擎优化await的过程: