在了解异步任务前首先要清楚JS的执行顺序是什么,什么是同步任务,什么是异步任务,同步任务和异步任务有什么区别。
JS的执行顺序
同步任务 → 异步任务 → 微任务 → 宏任务
在这里会引出一个概念叫事件循环
,先来看下面这张图
栈(stack)
:函数调用会形成一个栈的调用
function foo(b) {
let a = 10;
return a + b + 11;
}
function bar(x) {
let y = 3;
return foo(x * y);
}
console.log(bar(7)); // 返回 42
当调用bar()
函数时,会被压入栈,当bar()
调用foo()
时,再将foo()
压入栈,按照栈的后入先出原则进行执行。
堆(heap)
:对象会被分配到堆中。
队列(queue)
:这块主要执行的是异步任务
;在事件循环
期间,运行顺序是按照先入先出的顺序进行执行,被移出任务队列。
事件循环
:同步任务进入主线程,异步任务进入任务队列,主线程内的任务执行完毕为空后,去执行任务队列中的异步任务(在这块是先执行微任务,再执行宏任务),将当前任务队列中执行的一个任务推入主线程执行。上述描述不断地循环就是事件循环
同步任务
后一个任务需要等待前一个任务执行完之后才执行,按程序的顺序进行执行。
异步任务
每一个任务都会有一个或者多个回调函数,前一个任务执行后,不是执行后一个任务而是执行前一个任务的回调函数,后一个任务也不等待前一个任务的回调函数,而是直接执行;执行的顺序与任务的排序无关,不按程序的顺序执行。
在异步任务中又分为宏任务
和微任务
,他俩的执行顺序是先执行微任务再执行宏任务。
宏任务
包括:setTimeout,setInterval,Ajax,DOM事件
微任务
包括:Promise,async/await
简单的代码输出展示了同步与异步的执行顺序:
console.log(100) // 同步任务
setTimeout(()=>{ // 异步任务(宏任务)
console.log(200)
})
Promise.resolve().then(()=>{ // 异步任务(微任务)
console.log(300)
})
console.log(400) // 同步任务
// 答案为: 100 400 300 200
搞懂了什么是异步任务,来了解一下异步的解决方案有什么。
1、回调函数
2、promise
3、async/await
在这里对回调函数不做过多阐述,重点说一下promise和async/await。
Promise
Promise 对象用于表示一个异步操作的最终完成(或失败)及其结果值。
const promise = new Promise((resolve, reject) => {
console.log("初始化");
resolve(() => {
console.log("hello promise");
})
})
.then((e) => {
e()
})
.catch(e => {
console.log(e);
})
// 输出结果
// 初始化
// hello promise
promise和.then()返回的都是一个promise对象,所以可以运用链式调用。如果then链或者promise失败则会调用catch来捕获错误。then链中的调用相当于我们之前写的回调函数,有了promise的then链后,避免了回调地狱的发生。
async await
async表示异步,await表示为等待的意思。
async function fn1 (){
console.log(1)
try{
await fn2()
}catch(error) {
console.log(error)
}
console.log(2) // 被阻塞
}
async function fn2 (){
console.log('fn2')
}
fn1()
// 结果
// 1
// fn2
// 2
从结果可以看的出来,await会阻塞后面的代码,等await执行后才会执行后面的。并且await返回的也是一个promise对象,因此他也可以通过调用then链执行回调(异步)。在这里我建议大家在使用await的时候记得加try...catch
,这样可以不会错误,如果只是单纯的使用await,那么在报错时,不仅会阻塞后面的业务逻辑,而且也没有任何的错误提示。