事件队列机制
入门事例
setTimeout改变执行顺序
setTimeout(()=>{
console.log('2');
},0);
console.log('1');
// 输出1 2
- setTimeout会把其内部代码放到下一个事件循环任务队列的队列头部执行
- 意味着事件循环每次执行一个任务,任务之间顺序隔离
- 注意:setTimeout为0在js中实际是4ms,在操作dom时是16ms,而requestAnimationFrame的渲染效率则更高,专门为动画设计,但该执行顺序问题与4ms间隔无关
for循环的常见问题
for (var i = 0; i < 5; i++) {
setTimeout(function() {
console.log(new Date, i);
}, 1000);
}
console.log(new Date, i);
- 这里常见的误解是输出为1 2 3 4 5,造成该误解的原因是不了解js的异步机制
- 另一种误解是:预解析阶段for循环相当于全部执行完,i为5,function待执行,效果如下:
var i = 5;
setTimeout(function() { console.log(new Date, i);}, 1000);
setTimeout(function() { console.log(new Date, i);}, 1000);
setTimeout(function() { console.log(new Date, i);}, 1000);
setTimeout(function() { console.log(new Date, i);}, 1000);
setTimeout(function() { console.log(new Date, i);}, 1000);
console.log(new Date,i);
预解析只是提升变量作用域,不包括代码执行,这个问题用“预解析”理解为上述形式歪打正着地解释了这个问题
- 实际上该问题也是用事件循环和任务队列来解释的
事件循环概念
简介
- 由于js是单线程的,必须有一种机制来处理异步代码以防止主线程阻塞、提高效率,js提供的方案是事件循环(Event Loop)
- 任务队列实际上是所有任务的重新划分,规定同步任务和异步任务(包括事件回调、用户交互、UI 渲染和网络处理等行为)按什么顺序执行
- 每个任务都有一个任务源(task source),源自同一个任务源的 task 必须放到同一个任务队列,从不同源来的则被添加到不同队列
- 事件循环分两种运行环境,一种是常见的浏览器环境,以下介绍此种;一种是H5新的线程使用机制web worker标准,更适用于耗CPU的计算
工作原理
微任务与宏任务
- 不存在宏任务macrotask,根据规范,实际叫task,按task划分每个tick的执行代码
- 微任务microtask指的是同一次tick内同一个宏任务中的小异步操作,如promise
- (macro)task主要包含:script(整体代码)、setTimeout、setInterval、I/O、UI交互事件、postMessage、MessageChannel、setImmediate(Node.js 环境)
- microtask主要包含:Promise.then、MutaionObserver、process.nextTick(Node.js 环境)
- 微任务在宏任务之后紧随宏任务之后,理解上可以认为在宏任务“之中”
工作流程
- 主线程运行的时候,产生堆(heap)和栈(stack),堆中放的是变量常量,如for循环结束后的i的值,栈中的代码调用各种外部API
- 这些外部API在"任务队列"中加入各种事件(click,load,done)
- 只要栈中的代码执行完毕,主线程就会去读取"任务队列",依次执行那些事件所对应的回调函数。
- 主线程重复执行123步
Promise题目
题目一
new Promise(resolve => {
resolve(1);
Promise.resolve().then(() => {
// t2
console.log(2)
});
console.log(4)
}).then(t => {
// t1
console.log(t)
});
console.log(3);
- 同步脚本执行:new构造函数使得先输出4,构造函数中微任务队列内容靠后
- 继续执行同步脚本,执行栈中输出 3
- 微任务队列内容按先后顺序执行 2 1
- Promise.resolve 方法允许调用时不带参数,直接返回一个resolved 状态的 Promise 对象
- 立即 resolved 的 Promise 对象,是在本轮“事件循环”(event loop)的结束时,而不是在下一轮“事件循环”的开始时。
题目二
setTimeout(function() {
console.log('setTimeout1');
Promise.resolve().then(function() {
console.log('promise1');
}).then(function() {
console.log('promise2');
})
}, 0);
setTimeout(function() {
console.log('setTimeout2');
Promise.resolve().then(function() {
console.log('promise3');
}).then(function() {
console.log('promise4');
})
}, 0);
/*
setTimeout1
promise1
promise2
setTimeout2
promise3
promise4
*/
- 宏任务中注册的微任务会在宏任务中执行
- 微任务可以认为是宏任务中的普通script代码
题目三
const first = () => (new Promise((resolve,reject)=>{
console.log(3);
let p = new Promise((resolve,reject)=>{
console.log(7);
setTimeout(()=>{
console.log(5);
resolve(6);
},0)
resolve(1);
});
resolve(2);
p.then((arg)=>{
console.log(arg);
});
}));
first().then((arg)=>{
console.log(arg);
});
console.log(4);
// 3 7 4 1 2 5
- 先执行script同步代码 3 7 4
- 再执行微任务队列中的代码 1 2
- 最后执行下一个任务队列或者事件循环中的setTimeout 5
题目四
async function timeout() {
return 'hello world'
}
timeout();
console.log('虽然在后面,但是我先执行');
这个例子用来说明,async函数return的值会被包装到promise的resolve参数中输出,如果async中不存在return则async的promise返回值为undefined;如果async内出错则reject返回promise对象
题目五
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');
/*
script start // 整个代码作为script读入,同步代码script start先输出
async1 start // 异步函数构造函数内部代码输出
async2 // await构造函数代码输出 async2
promise1 // await让出线程,new Promise构造函数内代码输出
script end // 同步代码script end输出
promise2 //退出async后promise返回输出promise2
async1 end // 外部代码执行完后回到await处拿到promise值并向后执行输出async1 end
settimeout //下一个队列首部setTimeout输出
*/
有关说明:
- 流程
- await是一个让出线程的标志,即遇到await时,主线程执行await后的构造函数(同步部分)并跳出当前async函数执行当前调用栈的其他代码
- 后面代码执行完了之后又会跳回到async函数中等待await的结果
- 其他代码中promise微任务理解为与普通同步代码“同级”,promise执行完后才会执行await返回后的代码
- 注意点
- Promise是一个立即执行函数,但它他的成功(或失败:reject)的回调函数resolve却是一个异步执行的回调。当执行到resolve()时,这个任务会被放入到回调队列中,等待调用栈有空闲时事件循环再来取走它
- await和async可以看做优先级低于setTimeout宏任务的任务,async/await优先级高于promise
- await后面表达式的结果,如果返回值为非promise则继续执行async函数后面的代码,否则将返回的promise放入promise队列
- await返回 Promise对象的处理结果。如果等待的不是 Promise 对象,则返回该值本身。
评论 0
还没有评论,来抢个沙发吧。