js event loop、promise、async/await

event loop 事件循环/事件轮询

首先,js是单线程运行的,异步要基于回调来实现。event loop就是异步回调的实现原理。
js是如何执行的?
①从前到后,一行一行执行;
②如果某一行执行报错,则停止后面代码的执行;
③先把同步代码执行完,再执行异步;

看一个:

console.log('Hi');
setTimeout(function() {
    console.log('夏洛');
}, 5000);
console.log('Bye');

event loop总结:
①同步代码,一行一行放在Call Stack执行
②遇到异步,会先“记录”下,等待时机(定时、网络请求等)
③时机到了,就移动到Callback Queue
④如果Call Stack为空,即同步代码都执行完了,Event Loop开始工作
⑤轮询查找Callback Queue,如有则移动到Call Stack执行
⑥继续轮询查找(永动机一样)

(有时间上图)

ps: 异步(setTimeoutajax等)使用回调,基于event loop,DOM事件也使用回调,也基于event loop,但是DOM事件不是异步。

Promise有三种状态的变化

  • 三种状态
    pendingresolvedrejected
    pending -> resolvedpending -> rejected
    这几个状态,一旦改变,就不会再变化,是不可逆的。
  • 状态的表现和变化
    pending状态,不会触发thencatch
    resolved状态,会触发后续的then回调函数。
    rejected状态,会触发后续的catch回调函数。
  • thencatch对状态的影响
    两句hin牛逼的真言:
    then正常返回resolved,里面如果有报错则返回rejected
    catch正常返回resolved,里面如果有报错则返回rejected

是不是有点懵,好吧我懵了。但是看了下面结合这两句话我又好了:

const p1 = Promise.resolve().then(() => {
    return 100;
});
console.log('p1', p1); // resolved 触发后续 then回调
p1.then(() => {
    console.log('123'); // 会执行
})

// ------------------------------------------

const p2 = Promise.resolve().then(() => {
  return new Error('then error');
});
console.log('p2', p2); // rejected 触发后续 catch回调
p2.then(() => {
     console.log('456'); // 不会执行
}).catch((err) => {
    console.log('err100', err); // 会执行
})

// ------------------------------------------

const p3 = Promise.reject('my error').catch(err => {
    console.error(err);  
});
console.log('p3', p3); // resolved 注意!触发 then 回调
p3.then(() => {
    console.log(100); // 可以打印
})
// ------------------------------------------

const p4 = Promise.reject('my error').catch(err => {
    throw new Error('catch error'); 
});
console.log('p4', p4); // rejected 触发 catch 回调
p4.then(() => {
  console.log(200); // 不会打印出来
}).catch(() => {
    console.log('some err'); // 可以打印
}) // resolved 因为catch没有报错,返回的是resloved,不是rejected

是不是有点明白了?记住thencatch都会返回resloverejected就好了。
下面来几道题巩固一下:

// 第一题
Promise.resolve().then(() => {
    console.log(1); // 1
}).catch(() => {
    console.log(2);
}).then(() => {
    console.log(3); //2
}); 
// 1
// 3
// 最终的结果是一个resolved的promise
// 第二题
Promise.resolve().then(() => { // rejected
    console.log(1); // 1
    throw new Error('error1');
}).catch(() => { // resolved
    console.log(2); // 2 
}).then(() => {
    console.log(3); // 3
}); // resolved
// 1
// 2
// 3
// 最终的结果是一个resolved的promise
// 第三题
Promise.resolve().then(() => { // rejected
    console.log(1); // 1
    throw new Error('error1');
}).catch(() => { // resolved
    console.log(2); // 2
}).catch(() => {
    console.log(3); // 不执行,因为上一个catch中没有报错,上一步返回的是resloved,所以这部的catch不执行
})
// 1
// 2
// 最终的结果还是一个resolved的promise

async/await

我们都知道,异步回调存在callback hell,promisethencatch的链式调用解决了callback hell,但是也是基于回调函数。而asyncawait是用同步的语法,写异步的代码啊,来彻底消灭回调函数。

async/awaitpromise的关系:
①执行async函数,返回的是promise对象,不管它后面的是一个值,还是一个promise对象;
await相当于promisethen,但是它处理不了catch
try...catch可捕获异常,代替了promisecatch

看一个async/await的一个比较基础的例子,加深理解~
请写出console的打印顺序:

async function async1() {
    console.log('async1 start'); // 2
    await async2();
    console.log('async1 end'); // 5
    await async3();  
    console.log('async2 end 2'); // 7
};

async function async2() {
    console.log('async2'); // 3
};

async function async3() {
    console.log('async3'); // 6
};

console.log('script start'); // 1
async1();
console.log('script end'); // 4

分析一波:
①函数从上之下依次执行,显示几个函数定义,不用管他,下面先打印“script start”;
②调用函数async1,执行async1中的代码,打印 “async1 start”;
③接着遇到await函数,先执行await后面的函数async2,函数async2中打印 “async2”;
await后面的所有代码,都可以看做是callback里的内容,即异步,(可以理解为我们把后面的所有的代码放进一个队列等待),所以我们这时还是执行同步代码,即执行最后一个console,打印 “script end”;
⑤这时,所有的同步的代码都执行完啦,我们就要去刚才的队列里执行那些代码,把他们放入callback queue中,event loop去循环执行。所以这时候应该打印async1 end
⑥这时又来一个await,我们还是先将其后面的函数async3执行,这时打印 “async3”;
await后面的代码又异步了,我们又把它们放入队列里等待执行,然后我们执行同步代码。然鹅?已经木有同步代码啦,所以我们event loop一下,执行console,打印 “async2 end 2”。

for...of

for...in以及forEachfor是同步遍历,这都是常规操作,国际惯例。
for...of常用于异步的遍历

function muti(num) {
    return new Promise(resolve => {
        setTimeout(() => {
            resolve(num * num);
        }, 1000)
    })
}
const nums = [1, 2, 3];

// 同步循环
nums.forEach(async (i) => {
    const res = await muti(i);
    console.log(res); // 1秒后输出所有值
});

// 异步循环
(async function() {
    for(let i of nums) {
        const res = await muti(i);
        console.log(res); // 先输出1 再输出4 再输出9
    }
})();

宏任务和微任务

宏任务:setTimeoutsetIntervalAjaxDOM事件
微任务:Promiseasync/await
ps:微任务执行时机比宏任务要早

为什么呢?
先了解一下event loopDOM渲染的关系:
其实在每次call stack清空(同步任务执行完),event loop将要启动时,都是DOM重新渲染的机会,DOM结构如有改变则重新渲染。然后再去触发下一次event loop。

宏任务在DOM渲染后触发,如:setTimeout
微任务在DOM渲染前触发,如:promise
event loop原理解释一下,为何微任务执行更早:
宏任务放入任务队列时,是放入callback queue中,微任务放入任务队列时,是放入一个叫micro task queue的队列中,二者不属于同一队列。同时,宏任务是由浏览器规定的,而微任务是由ES6语法规定的,二者不一样,存放的对方自然就不一样了。

所以,在event loop机制中,当call stack清空时,要先执行当前的微任务,然后再尝试DOM渲染,接着执行宏任务,最后触发event loop。

最后来一道面试题:
还是看console的输出顺序

async function async1() {
    console.log('async1 start'); // 2
    await async2();
    console.log('async1 end');  // 微任务  6
}

async function async2() {
    console.log('async2'); // 3
}

console.log('script start'); // 1

setTimeout(function() { // 宏任务
    console.log('setTimeout'); // 8
}, 0)

async1();

// 初始化promise时,传入的函数会立刻被执行
new Promise(function(resolve) {
    console.log('promise1'); // 4
    resolve();
}).then(function() { // 微任务
    console.log('promise2'); // 7
});

console.log('script end'); // 5

你可能感兴趣的:(js event loop、promise、async/await)