一、什么是同步和异步?
同步 (sync):A任务执行完毕才能去执行B任务。
异步 (async):A任务和B任务可以同时进行。(
说明大家都知道,JS是单线程(single threaded)的,所以JS本身不可能是异步的,JS中的异步编程是利用宿主环境,如浏览器、node通过某种方式使之具备了异步的属性)。
举个栗子,在银行排号办理业务,叫到C时,他正在接一个重要电话,暂时不能去办理业务,这时
- 如果用同步编程的处理方式就是:C后面办理业务的人都得等着,等着他把电话接完,去办理业务,只有C办完了业务才能轮到后面的人,这个电话也不知道要说多久的。
-
但是用异步编程的处理方式的话:C在打电话,就先去旁边等着,等其他人都办理完业务后,C也打完了电话,再到C办理。
同步异步图解
二、为什么要在JS中使用异步模式编程?
JS 是单线程的语言,所以如果在JS的线程中出现非常耗时的操作,就容易堵塞后面任务的执行,因此如果碰上可能耗时的操作,比如setTimeout,ajax的回调函数(异步操作),就会将其放到一个代办任务队列中,当JS按顺序执行完其他同步任务后,再依次去执行任务队列中的任务。
三、JS中的常见的异步模式有哪些?
- 回调函数
- 事件监听
- 发布/订阅模式(又称观察者模式)
- promise
- Generator
- async/await
四、JS怎么实现异步操作的?
答案是:JS的事件循环机制(Event Loop)
也就是说:
当js解析执行时,有两类任务:同步任务 和 异步任务
同步任务 被推到执行栈按顺序执行。
异步任务 会被放到一个 任务队列(task queue) 中,等待JS引擎去执行。
当 执行栈 中所有的同步任务完成后,JS 引擎才会去 任务队列 中查看是否有任务存在,并将任务放到 执行栈 中去执行,执行完后又再去 任务队列 中查看是否有可以执行的任务。 这种循环检查的机制,叫做 事件循环(Event loop)
对于任务对列 ,又被分为 微任务(microtask)队列 和 宏任务(macrotask)队列
宏任务setTimeout 、setInterval 等 ,会被放到宏任务队列
微任务Promise的then 、Mutation Oberver、process.nextTick 等, 会被放到微任务队列
Event loop的执行顺序是:1、先执行 执行栈 里的任务。
2、执行栈 清空后,检查 微任务队列 ,将可执行的 微任务 全部执行。
3、取出 宏任务队列 中的第一项执行。
4、回到第二步。
通过代码来理解一下
console.log(1)
// 虽然是0秒,但是setTimeout里是回调函数为异步程序,会被放到 任务队列 里的 宏任务队列
setTimeout(()=>{
console.log(2)
},0)
console.log(3)
// 运行结果
132
console.log(1)
// 放到 任务队列 里的 宏任务队列
setTimeout(()=>{
console.log(2)
},200)
console.log(3)
// 放到 任务队列 里的 宏任务队列
setTimeout(()=>{
console.log(4)
},100)
console.log(5)
// 执行完 同步代码 后,检查 宏任务队列 中的第一项,如果还没有达到时间, 就进入下一次的 EventLoop , js内部会不断去查找是否有可以执行的程序
// 运行结果
13542
// 放到 任务队列 里的 宏任务队列
setTimeout(()=>{
console.log(1)
},0)
new Promise((resolve)=>{
console.log(2);
resolve();
// Promise的then 放到 任务队列 里的 微任务队列
}).then(()=>{
console.log(3)
})
console.log(4)
// 运行结果
2431
升级难度
console.log('1');
setTimeout(function() {
console.log('2');
process.nextTick(function() {
console.log('3');
})
new Promise(function(resolve) {
console.log('4');
resolve();
}).then(function() {
console.log('5')
})
},0)
process.nextTick(function() {
console.log('6');
})
new Promise(function(resolve) {
console.log('7');
resolve();
}).then(function() {
console.log('8')
})
setTimeout(function() {
console.log('9');
process.nextTick(function() {
console.log('10');
})
new Promise(function(resolve) {
console.log('11');
resolve();
}).then(function() {
console.log('12')
})
},0)
// 运行结果
1、7、6、8、2、4、9、11、3、10、5、12
上述结果解析
1、先是第一次事件循环(Event loop), 根据先执行同步,再执行微任务队列,得出
console.log('1');
process.nextTick(function() {
console.log('6');
})
new Promise(function(resolve) {
console.log('7');
resolve();
}).then(function() {
console.log('8')
})
// 运行结果
1768
2、再进行第二次事件循环(Event loop),由于时间间隔都是0,两个setTimeout 任务都进入了 宏任务队列
setTimeout(function() {
console.log('2');
process.nextTick(function() {
console.log('3');
})
new Promise(function(resolve) {
console.log('4');
resolve();
}).then(function() {
console.log('5')
})
},0);
setTimeout(function() {
console.log('9');
process.nextTick(function() {
console.log('10');
})
new Promise(function(resolve) {
console.log('11');
resolve();
}).then(function() {
console.log('12')
})
},0)
又可以看做
console.log('2');
process.nextTick(function() {
console.log('3');
})
new Promise(function(resolve) {
console.log('4');
resolve();
}).then(function() {
console.log('5')
})
console.log('9');
process.nextTick(function() {
console.log('10');
})
new Promise(function(resolve) {
console.log('11');
resolve();
}).then(function() {
console.log('12')
})
// 同步流程输出结果
2、4、9、11
// 执行微任务时输出结果 ,微任务中存在nextTick和回调函数,在这里需要注意,nextTick的执行顺序>回调函数
3、10、5、12
// 所有最终结果是
1、7、6、8、2、4、9、11、3、10、5、12