事件循环
原理
任务分类
- 同步任务:执行不需要等待,直接返回。在主线程上执行
- 异步任务:不会在主线程上执行,而是由 Web APIs 模块处理,该模块会在任务完成后将回调函数送入任务队列。任务队列可以分为两类
- 宏任务队列:保存宏任务,特点是需要较长时间完成,比如
setTimeout,setInterval,I/O,Ajax,Dom事件等 - 微任务队列:保存微任务,特点是能在较短时间完成,比如
Promise.then,async/await,nextTick等
- 宏任务队列:保存宏任务,特点是需要较长时间完成,比如
事件循环工作原理:
- 从上往下执行代码,遇到同步代码直接执行,遇到异步代码则交给 Web APIs 模块处理
- Web APIs 模块:由相应的处理线程(比如定时器线程、IO线程)处理异步逻辑,处理完后将回调函数包装为异步任务加入到任务队列
- 当所有同步代码执行完后,开始处理任务队列中的任务
一个示例
注意点一:执行 new Promise(executor_fun).then(ok_fun, fail_fun) 时,这个 executor_fun 函数会立即同步执行
<script>
setTimeout(() => console.log("wait 0 s"), 0);//3
new Promise((resolve, reject) => {
console.log("promise");//1
// resolve()
reject()
}).then(() => console.log("promise.then"),
() => console.log("promise.catch")//2
);
</script>
注意点二:优先处理微任务任务队列中的任务
<script>
console.log("hello");//1
setTimeout(() => console.log("wait 0 s"), 0);//5
new Promise((resolve, reject) => {
console.log("promise");//2
//调用resolve时才会将then回调放入任务队列
resolve()
}).then(() => console.log("promise.then"));//4
console.log("hi");//3
</script>
注意点三:如果在执行宏任务时产生了新的微任务,这些新的微任务不会立即执行,而是被放入微任务队列中,在当前宏任务执行完毕后被依次执行
<script>
setTimeout(() => {
console.log("here is timeout-1");//2
new Promise((resolve, reject) => {
console.log("promise");//3
resolve()
}).then(() => console.log("promise.then"));//4
}, 0);
setTimeout(() => {
console.log("here is timeout-2");//5
}, 0);
console.log("start!");//1
</script>
总结:在每个宏任务处理完后都会检查微任务队列是否有任务,如果有则处理完所有微任务才会继续处理宏任务队列
注意点四:执行微任务过程中产生的微任务不会推迟到下一个循环中执行
<script>
new Promise((resolve, reject) => {
console.log("1.executor-1");
resolve()
}).then(() => {
console.log("4.onfulfilled-1");
});
new Promise((resolve, reject) => {
console.log("2.executor-2");
resolve()
}).then(() => {
console.log("5.onfulfilled-2");
setTimeout(() => {
console.log("finally, setTimeout.callback");
}, 0);
new Promise((resolve, reject) => {
console.log("6.executor-4");
resolve()
}).then(() => {
console.log("8.onfulfilled-4");
//执行微任务过程中产生的微任务不会推迟到下一个循环中执行
//如果这里是死循环,即微任务队列中一直有任务,那么永远无法结束本轮执行,也就无法执行到最终 setTimeout 的回调
for (let i = 0; i < 10; i++) {
new Promise((resolve, reject) => {
console.log(`i.${i}.executor-${i}`);
resolve()
}).then(() => {
console.log(`i.${i}.onfulfilled-${i}`);
})
}
})
});
new Promise((resolve, reject) => {
console.log("3.executor-3");
resolve()
}).then(() => {
console.log("7.onfulfilled-3");
});
</script>
概念明晰
参考 Event Loop, Web APIs, (Micro)task Queue
Web APIs
Web APIs,是浏览器为 Javascript 运行时提供的功能合集,例如
- 使用摄像头
- 获取传感器状态
- XMLHttpRequest API
- Fetch API
- 获取地理位置
- 定时器
- 等等
Web APIs 本质上充当 JavaScript 运行时和浏览器功能之间的桥梁,使我们能够访问信息并使用超出 JavaScript 自身能力的功能。
有一些 API 允许我们启动异步任务,并将运行时间较长的任务交给浏览器处理。具体的,在调用这类 API 时需要为它们设置处理器(handlers),浏览器会在任务最终执行完成时应用这些处理器。
事件循环
事件循环:持续检查调用栈是否为空;如果为空,那么说明所有同步任务都执行完了,于是开始从任务队列中获取任务执行
微任务队列
微任务队列专门用于处理的回调:
- Promise 处理器中的回调 (
then(callback),catch(callback)和finally(callback)) await之后执行的async修饰的函数体MutationObserver回调queueMicrotask回调
每次执行完宏任务队列中的单个宏任务并且调用堆栈为空后,事件循环会处理微任务队列中的所有微任务,然后再次继续执行下一个宏任务。这可以确保立即处理与刚刚完成的宏任务相关的微任务,从而保持程序的响应能力和一致性。
微任务还可以调度其他微任务,所以可能会出现创建无限的微任务循环的情况,导致无限期地延迟宏任务队列并冻结程序的其余部分。
Promise
Promise 构造器签名
new <any>(
executor: (
resolve: (value: any) => void,
reject: (reason?: any) => void
) => void
) => Promise<any>
executor 函数的作用:进行异步操作(比如请求网络、读写文件),并在操作结束后的回调中调用 resolve 或 reject 以通知 Web APIs 模块异步操作已经完成(,然后 Web APIs 模块会将对应的回调函数包装成微任务送入微任务队列)。示例
new Promise(
(resolve, reject) => {
//异步逻辑
fetch("https://baidu.com").then(res => resolve(res)).catch(err => reject('请求失败'))
}
);
Promise 对象包含主要数据:
- 状态(挂起、填充和拒绝)
- 数据
- then 处理方法集合和 then 处理方法集合
- 是否被处理
参考 JavaScript Visualized - Promise Execution - YouTube
Promise 对象的三个方法(重点:几个参数,每个参数的作用,参数是否可省略,返回值类型)
Promise<any>.then<void, never>(
onfulfilled?: ((value: any) => void | PromiseLike<void>) | null | undefined,
onrejected?: ((reason: any) => PromiseLike<never>) | null | undefined
): Promise<...>
Promise<any>.catch<void>(
onrejected?: ((reason: any) => void | PromiseLike<void>) | null | undefined
): Promise<any>
Promise<any>.finally(
onfinally?: (() => void) | null | undefined
): Promise<any>
一个示例
<script>
let p = new Promise((resolve, reject) => {
setTimeout(() => {
console.log('1');
resolve('200');
}, 500)
});
console.log(p);
// promise.then
p.then(res => {
console.log('p.then', res);
})
p.then(res => {
console.log('p.then.0', res);
}, err => {
console.log('p.then.1', err);
})
// promise.catch
p.catch(err => {
console.log('p.catch', err);
})
// promise.finally
p.finally(() => {
console.log('p.finally');
})
</script>
async/await 语法是 Promise 的语法糖
async 修饰的函数返回类型是 Promise,函数体是 executor
<script>
async function test() {
console.log('1');
return '200';
}
let p = test();
//以上代码等于
// let p = new Promise((resolve, reject) => {
// console.log('1');
// resolve('200')
// })
console.log(p);
p.then(res => {
console.log(res);
})
</script>
await:用来将异步任务转化为同步任务;只能在 async 函数中使用 await 语法
<script>
//模拟一个异步操作
function a_secret_async_fn() {
return new Promise((resolve, reject) => {
setTimeout(() => {
resolve('ok');
}, 500);
});
}
//异步处理
a_secret_async_fn().then(res => {
console.log('ok' === res);
}, err => {
console.log('err', err);
})
//使用 await 同步处理
async function test() {
let res = await a_secret_async_fn();
console.log('res', res);
}
test();
</script>
几个示例
一
<script>
console.log('1')
setTimeout(function (){
console.log('2')
}, 1000)
console.log('3')
</script>
二
<script>
console.log('1')
setTimeout(function callback(){
console.log('2')
}, 1000)
new Promise((resolve, reject) => {
console.log('3')
resolve()
})
.then(res => {
console.log('4');
})
console.log('5')
</script>
三
<script>
setTimeout(() => {
console.log('1')
Promise.resolve().then(() => {
console.log('2')
})
},0)
new Promise((resolve, reject) => {
console.log('3')
resolve()
})
.then(res => {
console.log('4');
})
console.log('5')
</script>
四
<script src="https://cdn.jsdelivr.net/npm/vue@2/dist/vue.js"></script>
<script>
var app = new Vue({
el: '#app',
data: {
message: 'Hello Vue!'
}
})
setTimeout(function () {
console.log('定时器setTimeout')//5
}, 0);
new Promise(function (resolve) {
console.log('同步任务1');//1
for (var i = 0; i < 10000; i++) {
i == 99 && resolve();
}
}).then(function () {
console.log('Promise.then')//3
});
app.$nextTick(() => {
console.log('nextTick')//4
})
console.log('同步任务2');//2
</script>
五
<script>
for (var i = 0; i < 5; i++) {
setTimeout(function() {
console.log('异步任务', i);
}, 100);
}
console.log('同步任务', i);
</script>
六
<script>
for (let i = 0; i < 5; i++) {
setTimeout(function() {
console.log('异步任务', i);
}, 100);
}
console.log('同步任务', i);
</script>
示例:promise、async 和 await
<script>
console.log('1.', new Promise((resolve, reject) => {
resolve()
}));
console.log('2.', new Promise((resolve, reject) => {
}));
async function test() {
return 1
}
console.log('3.', test());//3
let p2 = test();
let p3 = p2.then(res => console.log('6.', res));//6
console.log('4,', p3);//4
async function call_test() {
console.log("5");
let x = await test();
console.log('7.', x);
}
call_test();
</script>