【十一】JavaScript执行(一):Promise里的代码为
首先我们要形成一个感性的认知:一个JS引擎会常驻于内存中,等待着宿主把JS代码或者函数传递给它执行。
在ES3或者更早的版本中,JS本身是没有异步执行代码的能力的,也就是说,宿主环境传递给JS引擎一段代码,引擎就把代码直接顺次执行了,这个任务也就是宿主发起的任务。
但是,ES5之后,JS引入了Promise,如此而来,JS引擎本身也可以发起任务了
那么采纳JSC引擎的术语,我们把宿主发起的任务叫宏观任务,把JS引擎发起的任务叫微观任务。
宏观和微观任务
JS引擎等待宿主环境分配宏观任务,在node术语中,叫做事件循环
整个循环做的事情基本上就是反复“等待-执行”,当然实际的代码中并没有那么简单,还有要判断循环是否结束、微观任务队列等逻辑。
在宏观任务中,JS的Promise还会产生异步代码,JS必须保证这些异步代码在一个宏观任务中完成,所以每个宏观任务又包含了一个微观任务队列:
image.png
有了宏观任务和微观任务机制,就可以实现JS引擎级和宿主机的任务了,例如:promise永远在队列尾部添加微观任务。setTimeout等宿主API,则会添加宏观任务。
Promise
promise是JS语言提供的一种标准化的异步管理方式,总体思想是,需要进行io、等待或者其他异步操作的函数,不返回真实结果,而返回一个“承诺”,函数的调用方可以在合适的时机,选择等待这个promise兑现(通过promise的then方法回调)
promise的基本用法:
// 等候传入参数指定的时长
function sleep(duration) {
return new Promise(function(resolve, reject) {
setTimeout(resolve,duration);
})
}
sleep(1000).then(()=> console.log("finished"));
promise的then回调是一个异步的执行过程,下面我们就来研究一下promise函数中的执行顺序,例如:
var r = new Promise(function(resolve, reject){
console.log("a");
resolve()
});
r.then(() => console.log("c"));
console.log("b")
输出结果为a b c。在进入console.log("b")
之前,r已经得到了resolve,但是promise的resolve始终是异步操作,所以c无法出现在b之前。
在看一个:
var r = new Promise(function(resolve, reject){
console.log("a");
resolve()
});
setTimeout(()=>console.log("d"), 0)
r.then(() => console.log("c"));
console.log("b")
输出结果为a b c d。因为promise是JS引擎内部的微任务,而setTimeout是浏览器API,是宏任务。
为了便于理解微任务始终先于宏任务,在看一个例子:
setTimeout(()=>console.log("d"), 0)
var r = new Promise(function(resolve, reject){
resolve()
});
r.then(() => {
var begin = Date.now();
while(Date.now() - begin < 1000);
console.log("c1")
new Promise(function(resolve, reject){
resolve()
}).then(() => console.log("c2"))
});
执行一个耗时1s的promise。可以确保c2是在d之后被添加到任务队列。
最后总结下如何分析异步执行的顺序:
- 首先分析有多少个宏任务
- 每个宏任务中,有多少个微任务
- 根据调用次序,确定宏任务中微任务的执行次序
- 根据宏任务的触发规则和调用次序,确定宏任务的执行次序
- 确定整个顺序
值得一提,promise是JS中的一个定义,但是实际编写代码时,它似乎并不比毁掉的方式书写简单,但是从ES6开始,我们有了async/await,这个语法改进跟promise配合,能够有效地改善代码结构
async/await
async/await是ES6新加入的特性,提供了for、if等代码结构来编写异步的方式。运行基础是promise。
async函数必定返回promise,特征是在function关键字前加上async关键字,这样就定义了一个async函数,接着可以使用await来等待一个promise
function sleep(duration) {
return new Promise(function(resolve, reject) {
setTimeout(resolve,duration);
})
}
async function foo(){
console.log("a")
await sleep(2000)
console.log("b")
}
async函数的强大在于它可以嵌套使用。
function sleep(duration) {
return new Promise(function(resolve, reject) {
setTimeout(resolve,duration);
})
}
async function foo(name){
await sleep(2000)
console.log(name)
}
async function foo2(){
await foo("a");
await foo("b");
}
小练习
需求:实现一个红绿灯,圆形的div按照绿色3s,黄色1s,红色2s循环改变背景色。
代码实现:
const lightEle = document.getElementById('traffic-light');
function changeTrafficLight(color, duration) {
return new Promise(function(resolve, reject) {
lightEle.style.background = color;
setTimeout(resolve, duration);
})
}
async function trafficScheduler() {
await changeTrafficLight('green', 3000);
await changeTrafficLight('yellow', 1000);
await changeTrafficLight('red', 2000);
trafficScheduler();
}
trafficScheduler();