JavaScript 中的同步和异步执行

385 阅读4分钟

一、什么是同步和异步?

同步 (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)队列

宏任务 setTimeoutsetInterval 等 ,会被放到宏任务队列

微任务 Promise的thenMutation Oberverprocess.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)

// 运行结果
176824911310512

上述结果解析

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')
    })

// 同步流程输出结果

24911

// 执行微任务时输出结果 ,微任务中存在nextTick和回调函数,在这里需要注意,nextTick的执行顺序>回调函数

310512

// 所有最终结果是
176824911310512