JS执行顺序问题
遇到setTimeout与promise、async的执行顺序问题 之前理解的比较懵懂 着重记录一下
js的事件循环(event loop【同步和异步】)
javascript是一门单线程语言,在HTML5中提出了Web-Worker(链接:Web-Worker介绍与使用),但javascript是单线程这一核心仍未改变。所以一切javascript版的"多线程"都是用单线程模拟出来的。所有的任务需要排队一个一个执行,当一个任务执行完毕后在执行下一个任务。 但是任务在执行的过程,由于IO设备很慢, 必须等待得到结果后才继续执行下一个任务,导致效率极低;此时cpu是处于空闲状态的,完全可以执行其它任务。 那么可以在执行任务的时候完全不顾IO设备,把等待的任务挂起,先执行其它任务,等结果出来后再返回去执行之前的任务,这样就可以提升效率了。 所有的任务可分为两种,一种是同步任务(synchronous),一种是异步任务(asynchronous)
- 同步任务 在主线程上排队执行的任务,只有前一个任务执行完毕,才会执行后一个任务
- 异步任务 不进入主线程,而是进入任务队列的任务,只有等主线程任务执行完毕,任务队列开始通知主线程,请求执行任务,该任务才会进入主线程
图示(事件循环event loop):
- 同步和异步任务分别进入不同的执行"场所",同步的进入主线程,异步的进入Event Table并注册函数
- 当指定的事情完成时,Event Table会将这个函数移入Event Queue
- 主线程内的任务执行完毕为空,会去Event Queue读取对应的函数,进入主线程执行
- 上述过程会不断重复,也就是常说的Event Loop(事件循环) 示例:
let data = [];
$.ajax({
url:
data:data,
success:() => {
console.log('发送成功!');
}
})
console.log('代码执行结束');
解析:
- ajax进入Event Table,注册回调函数success
- 执行console.log()
- ajax事件完成,回调函数success进入Event Queue
- 主线程从Event Queue读取回调函数success并执行
主线程【js一直在做一个工作,就是从任务队列里提取任务,放到主线程里执行】
-
所有同步任务都在主线程上执行,形成一个执行栈(execution context stack)。
-
主线程之外,还存在一个"任务队列"(task queue)。只要异步任务有了运行结果,就在"任务队列"之中放置一个事件。
-
一旦"执行栈"中的所有同步任务执行完毕,系统就会读取"任务队列",看看里面有哪些事件。那些对应的异步任务,于是结束等待状态,进入执行栈,开始执行。
-
主线程不断重复上面的第三步
setTimeout
- setTimeout执行时间与设定时间不对等导致原因
setTimeout(() => {
task()
},3000)
sleep(10000000)
解析:
- task()进入Event Table并注册,计时开始
- 执行sleep函数,很慢,非常慢,计时仍在继续
- 3秒到了,计时事件timeout完成,task()进入Event Queue,但是sleep也太慢了吧,还没执行完,只好等着
- sleep终于执行完了,task()终于从Event Queue进入了主线程执行
上述的流程走完,setTimeout这个函数,是经过指定时间后,把要执行的任务(task())加入到Event Queue中,又因为是单线程任务要一个一个执行,如果前面的任务需要的时间太久,那么只能等着,导致真正的延迟时间远远大于3秒。
2.setTimeout(fn,0) 代码是否可以立即执行
setTimeout(fn,0)的含义是,指定某个任务在主线程最早可得的空闲时间执行,意思就是不用再等多少秒了,只要主线程执行栈内的同步任务全部执行完成,栈为空就马上执行【即便主线程为空,0毫秒实际上也是达不到的。根据HTML的标准,最低是4毫秒】
js的事件循环(【宏任务 微任务】)
- macro-task(宏任务):包括整体代码script,setTimeout,setInterval
- micro-task(微任务):包括Promise,process.nextTick等
注:宏观任务优先级:主代码块 > setImmediate > MessageChannel > setTimeout / setInterval 比如:setImmediate指定的回调函数,总是排在setTimeout前面
微任务优先级:process.nextTick > Promise > MutationObserver
事件循环的顺序,决定js代码的执行顺序。进入整体代码(宏任务)后,开始第一次循环。接着执行所有的微任务。然后再次从宏任务开始,找到其中一个任务队列执行完毕,再执行所有的微任务。
示例:
setTimeout(function() {
console.log('setTimeout');
})
new Promise(function(resolve) {
console.log('promise');
resolve()
}).then(function() {
console.log('then');
})
console.log('console');
- 这段代码作为宏任务,进入主线程
- 先遇到setTimeout,那么将其回调函数注册后分发到宏任务Event Queue
- 接下来遇到了Promise,new Promise立即执行,then函数分发到微任务Event Queue
- 遇到console.log(),立即执行
- 整体代码script作为第一个宏任务执行结束,看看有哪些微任务?发现then在微任务Event Queue里面,执行
- 第一轮事件循环结束,开始第二轮循环,从宏任务Event Queue开始。发现了宏任务Event Queue中- --- setTimeout对应的回调函数,立即执行
Promise.resolve().then(() => {
console.log("1");
setTimeout(() => {
console.log("2");
}, 0);
});
setTimeout(() => {
console.log("3");
Promise.resolve().then(() => {
console.log("4");
});
}, 0);
- (红色):JS 引擎会把微观任务Promise存入执行栈,把宏观任务setTimeout存入 “任务队列”
- (绿色):主线程率先运行执行栈中的代码,依次输入1,然后把绿框的setTimeout存入 “任务队列”
- (蓝色):执行栈清空以后,会率先读取 “任务队列” 中最早存入的setTimeout(红框的那个),并把这个定时器存入栈中,开始执行。这个定时器中的代码都是微观任务,所以可以一次性执行,依次输出3 和 4
- (紫色):重复第3步的操作,读取 “任务队列” 中最后存入的setTimeout(绿框的那个),输出2
async 和 setimeout嵌套执行顺序问题
async与await转化
实际上async是promise的语法糖,我们要将其转换为promise,async会返回一个隐式的promise async function MDN的解释
async function async1(){
console.log('async1 start')
await async2()
console.log('async1 end')
}
async function async2(){
console.log('async2')
}
async1();
可以转成
function async1() {
console.log("async1 start");
const p = async2();
return new Promise((resolve) => {
resolve();
}).then(() => {
p.then(() => {
console.log("async1 end");
});
});
}
function async2() {
console.log("async2");
return new Promise((resolve) => {
resolve();
});
}
async1();
前几天遇到的问题
async function async1() {
console.log("async1 start");
await async2();
await setTimeout(function(){
console.log('111')
})
console.log("async1 end");
}
async function async2() {
console.log("async2");
}
async1();
并不会按顺序输出 输出结果为 // 输出 async1 start async2 async1 end 111
可以转成
function async1() {
console.log("async1 start");
const p = async2();
return new Promise((resolve) => {
resolve();
}).then(() => {
p.then(() => {
new Promise((resolve) =>{
setTimeout(function(){
console.log('111')
})
resolve();
}).then(() => {
console.log("async1 end");
})
});
});
}
function async2() {
console.log("async2");
return new Promise((resolve) => {
resolve();
});
}
async1();
- 这段代码作为宏任务,进入主线程
- 先遇到console.log("async1 start"); 执行 // async1 start
- 遇到了Promise,new Promise立即执行,then函数分发到微任务Event Queue 得到结果 // async2
- 整体代码script作为第一个宏任务执行结束,看看有哪些微任务?发现then在微任务Event Queue里面,执行,调用async2函数,遇到promise立即执行,将then函数分发到微任务Event Queue,
- 遇到Promise,new Promise立即执行,其中包含setTimeout,将其回调函数注册后分发到宏任务Event Queue,接着将then也分发到微任务Event Queue中
- 宏任务执行完毕,查看Event Queue中的微任务,执行 // async1 end _ 微任务执行完毕,在一循环执行宏任务Event Queue中的setTimeout // 111