JS同步异步编程及EventLoop机制

324 阅读4分钟

1. 浏览器线程

进程、线程

  • 进程代表的是一个程序(浏览器开一个页卡就是一个进程)
  • 线程是用来处理进程中的具体事物,如果一个进程中有很多事情要做,就可以开辟很多线程
  • 一个线程同时只能做一件事

浏览器是多线程的

  • GUI渲染线程
  • HTTP网络线程
  • JS渲染线程
  • 定时器监听线程
  • 事件触发线程

JS是单线程的:浏览器只分配一个线程来渲染JS代码

  • JS中的代码大部分都是同步编程,即上面的任务没有完成,下面的任务是无法处理的
  • 但是JS可以利用浏览器的多线程机制,规划出异步编程效果
    • 定时器、AJAX/Fetch/跨域(HTTP网络请求)、事件绑定、promise中的异步、Generator函数、async/await等
/*
 * 计算程序执行的时间(预估)
 *   + 运行监控 console.time/timeEnd(受当前电脑运行环境的影响)
 *   + 时间复杂度、大O表示法(提前预估)
 */
 
console.time('AAA');
for (let i = 0; i < 99999999; i++) {}
console.timeEnd('AAA');

死循环阻塞代码

while (true) {}
console.log('OK'); // 不执行:上述的死循环一直占用这“JS渲染线程”,线程空闲不下来,就处理不了其他的事情 

2. 定时器异步编程

2.1 定时器的同步异步

  • 设置定时器任务是同步的
  • 间隔interval这么长时间,执行定时器绑定的函数 这个任务是异步的
  • 遇到异步任务,浏览器不会等待它执行完,则继续渲染下面的代码;当等到下面代码运行完时间也到达了执行的条件,才会把异步任务执行
setTimeout(()=>{
	console.log('1');
},1000)
console.log('2');
// 2 1

interval设置为不是立即执行,而是浏览器都有最快反应时间(谷歌:5~6ms IE:13~17ms),设置为零,最快也需要等到5~6ms左右

setTimeout(() => {
    console.log('1');
}, 0);
console.log('2');
// 2 1

2.2 例题

setTimeout(() => {
    console.log(1);
}, 20);
console.log(2);
setTimeout(() => {
    console.log(3);
}, 10);
console.log(4);
console.time('AA');
for (let i = 0; i < 90000000; i++) {
    // do soming
}
console.timeEnd('AA'); //=>AA: 79ms 左右
console.log(5);
setTimeout(() => {
    console.log(6);
}, 8);
console.log(7);
setTimeout(() => {
    console.log(8);
}, 15);
console.log(9);

3. Promise异步编程

4. async/await异步编程

4.1 await中的异步

async/await是ES7中新提供的语法糖

  • async修饰一个函数:保证函数返回的是一个promise实例

    • 和then很相似,函数执行不报错,返回成功的promise实例,报错返回的是失败的
    • return的值或者报错的原因就是promise实例的结果
    • 如果return的是一个新的promise实例,则实例的结果影响返回值
  • async最常用的应用,是为了修饰函数,让函数中可以使用await(想要使用await,所在的函数必须是async修饰的

function func() {
    await fn();     //Uncaught SyntaxError: await is only valid in async function
} 
  • await [promise实例] 等待promise实例状态为成功的时候,再继续执行await后面的代码
  • await 修饰非promise实例的时候,直接算做成功
(async function () {
    let x = await 10;
    console.log(x); //10

    let y = await Promise.resolve(20);
    console.log(y); //20

    try {
        let z = await Promise.reject(30); //await后面的promise如果是失败的,则当前函数中await下面的代码都不会执行
        console.log(z);
    } catch (err) {
        // 基于try catch异常捕获,可以捕获到await后面的promise实例是失败状态下的失败信息(浏览器控制台不会再报错了)
        console.log(err); //30
    }
})(); 

4.2 例题

很多人认为async/await是同步操作,其实这种理解是不正确的,await修饰的操作虽然是同步执行的,但是他在asnyc修饰的上下文中还会有一个特殊的操作,那就是将await下面的代码全部转成微任务,并添加到Event Queue中的微任务中。这么说可能有的小伙伴不是非常的理解,我们通过一道字节跳动的面试题来讲解

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' 'async1 start' 'async2' 'promise1' 'script end' 'async1 end' 'promise2' 'setTimeout'

5. 同步异步编程综合

function func1(){
    console.log('func1 start');
    return new Promise(resolve=>{
        resolve('OK');
    });
}
function func2(){
    console.log('func2 start');
    return new Promise(resolve=>{
        setTimeout(()=>{
            resolve('OK');
        },10);
    });
}

console.log(1);
setTimeout(async () => {
    console.log(2);
    await func1();
    console.log(3);
}, 20);
for (let i = 0; i < 90000000; i++) {} //循环大约要进行80MS左右
console.log(4);
func1().then(result=>{
    console.log(5);
});
func2().then(result=>{
    console.log(6);
});
setTimeout(() => {
    console.log(7);
}, 0);
console.log(8);

代码的执行结果:1 4 'func1 start' 'func2 start' 8 5 2 'func1 start' 3 7 6