一、宏任务和微任务
宏任务和微任务是等待任务队列中的异步任务的处理机制,浏览器的任务队列中,主任务队列存储的都是同步任务,等待任务队列中存储的都是异步任务。
微任务:
- Promise 的 then 回调函数
- async 函数 await 下面的代码
- process.nextTick
宏任务:定时器(setInterval 和 setTimeout)
首先浏览器会把主动任务队列中的同步任务挨个全部执行完,然后再去等待任务队列中看那个任务可以执行了,然后把该执行的任务放到主任务队列中去执行,等这个任务执行完,再去等待任务队列中看谁可以执行了,再把这个任务放到主任务对了中去执行… 如此循环。这种循环叫做事件循环(Event Loop)
异步任务都是谁先到达条件谁先执行,但是谁先到达条件也有优先级的问题,这个优先级要看这个任务与是宏任务还是微任务,微任务的优先级高于宏任务。
setTimeout(function () {
console.log(1)
}, 0);
console.log(5);
let p = new Promise((resolve, reject) => {
console.log(2);
resolve(); // resolve 执行并不是让 then 里面回调函数立即执行,resolve 执行只是告诉 then 的回调函数到达执行条件了;而 then 里面的回调函数异步执行的,需要等着所有的同步任务执行结束才能执行它;
console.log(3);
});
console.log(6);
p.then(() => {
console.log(4)
});
console.log(7);
// 5 2 3 6 7 【主任务队列中的同步任务都执行完了】 4 1
???为什么4先输出了
因为定时器都是宏任务,then 里面的回调函数是微任务,因为微任务的优先级比宏任务的高,所以微任务先执行,所以先输出4
当主任务队列中的同步任务执行完,先去等待任务队列中吧所有的微任务找到,并且执行这些微任务;执行完,再去吧宏任务找到并且执行
二、async 和 await
async 和 await 是 es6 新增的关键字,用于把异步变成同步
1. async
async 在函数定义时使用,用 async 定义的函数默认返回一个 Promise 实例,可以直接 .then
async function fx () {
console.log(1);
}
let obj = {
async getName () {
// async 还可以定义对象的方法
}
};
fx().then(() => {
// 因为 async 函数返回了 Promise 实例,所以可以 .then
})
function g() {
return 'abc';
}
function h() {
return new Promise((resolve, reject) => {
setTimeout(() => {
resolve('xyz');
},300)
})
}
2. await (要和 async 一起使用)
async function fn() {
// await 等待,等右侧的代码执行完
// await 用法:
// let x = await g();
// console.log(x);
// 1. 如果 await 右侧是同步的代码,就会让同步代码执行,如果执行的是一个函数,还会把函数的返回值给到 await 左边的变量
let y = await h() {
console.log(y);
console.log('123');
}
// h().then((res) => console.log(res)) 有了 async 和 await,就不用写 then 了;因为 await 可以取得 promise resolve 时传入的值
// 2. 如果 await 右侧是一个 Promise 实例,或者一个方法返回了Promise 实例,await 会等着 Promise 的实例 resolve,并且在实例resolve 之前,await 后面的代码不执行;并且还会拿到 Promise 在 resolve 时传入的值,并且赋值给等号左侧变量;
// 3. await 会把 await 下面的代码变成微任务;
}
fn();
真实项目中,async 和 await 常常结合 AJAX 和 Promise 一起使用
需求:用 async 和 await 改写先请求 login.json 再请求 banner.json 并且再请求结束后渲染到页面中;
// await 后面常跟一个 Promise 实例或者跟返回的 Promise 实例的方法
function getLogin() {
return new Promise((resolve, reject) => {
let xhr = new XMLHttpRequest();
xhr.open('GET', 'login.json');
xhr.onreadystatechange = function () {
if (xhr.readyState === 4 && xhr.status === 200) {
resolve(JSON.parse(xhr.responseText));
}
};
xhr.send();
});
}
function getBanner(token) {
return new Promise((resolve, reject) => {
let xhr = new XMLHttpRequest();
xhr.open('GET', 'banner.json?token=' + token);
xhr.onreadystatechange = function () {
if (xhr.readyState === 4 && xhr.status === 200) {
resolve(JSON.parse(xhr.responseText));
}
};
xhr.send();
})
}
async function bindHTML() {
let login = await getLogin();
async function bindHTML() {
let login = await getLogin();
// 从登录信息中取得 token,把 token 传给 getBanner; (第二个接口依赖第一个接口中的内容,用 await 等前面的接口响应完,就可以拿到数据,然后把你想要的数据从里面拿出来,再传给下一个请求)
let { data: { token } } = login;
let banner = await getBanner(token);
console.log(login, banner);
// 这下面可以执行绑定数据的操作
// ....
// 这里可以写开启轮播图的事情
}
bindHTML().then(() => {
// 还可以在这个回调函数写开启轮播的事情
})