异步编程
JavaScript 是一种单线程语言,这意味着它一次只能执行一个任务。在同步编程模式下,任务会按顺序执行,每个任务必须等待前一个任务完成后才能开始。而异步编程允许程序在等待某个长时间操作(如网络请求、文件读取等)完成的同时继续执行其他任务。
简单来说:
- 异步:就是一个人一次干很多事情,不必等待每件事完成之后去做下一件
- 同步:一个干活 而且一次只能做一件事,必须等待上一件做完之后去做下一件
使用异步异步处理:
- 提升用户体验:避免页面冻结,保持界面响应性
- 提高性能:充分利用系统资源,不必等待耗时操作完成
- 处理I/O密集型任务:如网络请求、数据库查询、文件操作等
异步处理的原理
前提
js是单线程语言,同一时间只能执行一段代码
- js的同步代码会放在调用栈中逐行执行,如果有一段代码报错,就会阻塞后面的代码执行
- 异步需求:如果遇到像定时器,网络请求,DOM时间等耗时久或是非及时任务,不能阻塞主线程代码,则需要通过“先记录,后执行”的方式处理
Event Loop机制
简单理解
总结一下eventLoop 就是不断去监听异步任务队列 在同步任务处理完之后 处理异步任务 先处理微任务 再处理dom 最后处理宏任务队列中的一个宏任务(因为宏任务类似于js脚本,任务里面存在他自身的同步代码与异步代码)然后不断去循环这一步骤 直到调用栈为空
进程与线程
进程:操作系统里面的一个基本概念,表示正在运行的一个程序实例。每个进程都有自己独立的地址空间,包括代码,数据,堆栈等,同时还包括了进程所需的一系列系统资源,比如文件描述符,型号处理设置等。进程一般各自独立,彼此不会 影响
线程: 线程是进程中的一个实体,是cpu调度的分派的基本单位 一个进程中可以存在多个线程,这些线程共享进程的地址空间与系统资源,但每个线程都有自己的调用栈和局部变量。多线程可以让程序并发执行,提高程序的效率和响应速度
总结,进程是程序的执行实例,包含了程序的代码、数据和系统资源;而线程是进程中的执行单元,多个线程可以共享进程的资源,实现并发执行。
Event-Loop执行顺序
注:异步任务队列-->回调队列
- 所有代码进入调用栈
- 先执行当前宏任务(如初始
script)中的同步代码(脚本本身就是一个宏任务) - 在执行同步任务的过程中,如果遇见异步任务(如定时器,ajax,promise等) 会将异步任务注册到对应模块(如网络请求注册为网络模块)并等待触发条件
- 当注册后的异步任务的触发条件满足后,其回调函数会被放入对应的队列(微任务队列 / 宏任务队列)。
- 先执行当前宏任务(如初始
- 当同步代码执行完毕,调用栈为空时启动Event-Loop。
- Event-Loop遍历异步任务队列中的微任务队列,将所有微任务队列压入调用栈,直到微任务队列为空(过程中如果产生新的微任务也会被压入调用栈),此时执行栈也会同步执行执行代码
- 如果存在新的DOM需要渲染,或是DOM存在改动(DOM操作)浏览器就会执行dom的渲染
- DOM执行完毕之后,Event-Loop就会从从宏任务队列中取出第一个任务压入执行栈,执行其同步代码(过程中重复步骤 1 中异步任务的注册逻辑)
- 循环执行3-5,直到异步任务队列全部执行完毕。
回调队列
回调队列(Callback Queue)是 JS 异步机制中存储待执行回调函数的队列结构(遵循 “先进先出” 原则),用于暂存异步任务完成后需要执行的回调函数,等待主线程空闲时通过 Event Loop 调度执行。简单来说就是异步任务队列,回调对列中有两个子队列,微任务队列与宏任务队列。
核心作用
- 暂存异步回调:当异步任务(如 setTimeout、Ajax、DOM 事件)完成后,其对应的回调函数不会立即执行,而是先进入回调队列等待。
- 协调主线程调度:避免异步回调无序抢占主线程,通过队列的 “先进先出” 规则,保证回调函数按 “完成顺序” 被有序处理。
调用栈
调用栈(Call Stack)是 JS 引擎管理函数执行顺序的 “栈结构容器”,遵循「先进后出」规则,核心用来追踪当前正执行的函数、以及接下来要执行的函数。
核心特点
- 结构规则:先进后出,新函数执行时 “压入” 栈顶,执行完后 “弹出” 栈。
- 核心作用:记录函数调用关系,存储函数的局部变量、参数等执行上下文,确保代码按调用顺序执行。
为什么使用的是栈而不是队列呢,这跟函数中存在嵌套关系有着很大关系,比如下列代码
function A() {
console.log('A开始');
B(); // A内部调用B,B必须先执行完,A才能继续
console.log('A结束');
}
function B() {
console.log('B开始');
C(); // B内部调用C,C必须先执行完,B才能继续
console.log('B结束');
}
function C() {
console.log('C执行');
}
A();
使用栈:A执行入栈->打印'A开始'->调用B入栈->打印'B开始'->调用c入栈->'打印c开始'->c执行完出栈,b为栈顶->打印'b结束'->B执行完出栈,A为栈顶->打印'A结束'->A执行完出栈 此时调用栈为空
JS异步处理示意图
使用回调函数实现异步
原理
核心是通过回调函数衔接异步操作的结果,利用异步任务 “不阻塞主线程” 的特性,实现代码的高效执行,流程如下:
-
发起异步操作:
当执行异步任务(如 Ajax 请求、DOM 事件监听、定时器等)时,JS 引擎会将该任务交给浏览器的其他线程(如网络线程、定时器线程)处理,主线程不等待该任务完成,而是继续执行后续的同步代码。此时,异步任务在 “后台” 独立运行(耗时操作不占用主线程)。
-
同步代码优先执行:
主线程按顺序执行所有同步代码,直至执行完毕(调用栈清空,主线程处于空闲状态)。这一步保证了同步逻辑不被异步任务阻塞,优先完成。
-
异步任务完成后回调入队:
当 “后台” 的异步任务执行完毕(如 Ajax 拿到响应、用户触发了 DOM 事件、定时器到期),其对应的回调函数会被放入回调队列(等待执行的队列)。
-
主线程执行回调函数:
主线程空闲时,通过Event Loop(事件循环) 机制,从回调队列中取出回调函数,放入调用栈中执行。回调函数中包含对异步结果的处理逻辑(如渲染数据、执行下一步操作),最终完成整个异步流程。
异步操作 “后台” 执行→同步代码先跑→异步完成后回调入队→主线程空闲→执行回调
缺点
会造成回调地域,对代码的调试难度与可读性造成毁灭性打击
回调函数
需要注意的是,回调函数实现异步时,“嵌套” 是为了处理依赖关系(如 “先请求 A,再用 A 的结果请求 B”),但单纯实现一个异步操作不需要嵌套(如单个 Ajax 请求的回调)。嵌套更多是为了解决 “异步流程顺序” 问题,而非异步本身的实现必要条件。
const callback=(err,res)=>{
//err一般为抛出的错误对象 res一般为处理成功之后的结果
if (err) {
// 处理错误
console.log('请求失败:', err.message);
} else {
// 处理成功结果
console.log(res);
}
}
同步函数
function 异步操作(参数, callback) {
//模拟耗时操做如ajax,DOM操作等
setTimeout(()=>{
/*
执行同步逻辑代码
*/
// 检查是同步逻辑的运行结果
if (处理成功后的数据) {
// 成功:回调第一个参数为null(表示无错误),第二个参数为正确数据 会在回调函数中再处理
callback(null,处理后的数据);
} else {
// 失败:回调第一个参数为错误对象,第二个回调函数为null表示没有成功处理的数据
callback(new Error(`抛出错误`), null);
}
},1000)
}
//.....后续逻辑
console.log('开始执行同步逻辑')
异步操作(参数,callback)
console.log('已调用异步函数,等待异步响应完成')
console.log('继续执行同步逻辑')
执行上述代码,因为计时器的原因(因为计时器会在一秒之后执行,此时主线程属于空闲状态),不会阻塞主线程的执行,会先执行同步代码,输出'开始执行同步逻辑'然后执行异步函数,并执行后续的 '已调用异步函数,等待异步响应完成'与'继续执行同步逻辑'等待一秒之后异步操作处理完成,输出执行后的结果
例子
我们模拟执行一个网络请求的异步处理
// 模拟异步请求用户信息的函数(类似真实的API调用)
function fetchUserInfo(userId, callback) {
// 用setTimeout模拟网络请求的异步延迟(1.5秒)
setTimeout(() => {
// 模拟不同用户ID的请求结果
const users = {
101: { id: 101, name: '张三', age: 28 },
102: { id: 102, name: '李四', age: 32 }
};
// 检查用户是否存在
if (users[userId]) {
// 成功:回调第一个参数为null(无错误),第二个参数为用户数据
callback(null, users[userId]);
} else {
// 失败:回调第一个参数为错误对象,第二个参数为null
callback(new Error(`用户ID ${userId} 不存在`), null);
}
}, 1500); // 模拟1.5秒的网络延迟
}
// ------------------------------
// 使用异步函数:通过回调处理结果
// ------------------------------
console.log('开始请求用户信息...'); // 同步代码,立即执行
// 调用异步函数,传入用户ID和回调函数
fetchUserInfo(101, (error, user) => {
// 这个回调会在1.5秒后(网络请求完成)执行
if (error) {
// 处理错误(如用户不存在)
console.log('请求失败:', error.message);
} else {
// 处理成功结果(如展示用户信息)
console.log('请求成功,用户信息:', `姓名:${user.name},年龄:${user.age}`);
}
});
console.log('请求已发送,等待响应...'); // 同步代码,不阻塞,先于回调执行
Promise
基本使用
通过new关键字可以实例化一个Promise对象 ,调用的是Promise构造函数,参数是处理结果的回调函数
new Promise((resolve,rejects)=>{
//这里还是同步逻辑,后面的then就是异步处理了
通过逻辑函数处理数据{
resolve('成功'); //表示执行成功
rejects('失败');//报错失败
}
}).then(value=>{
//处理成功情况
console.log('成功1');
},reason=>{
//处理失败情况
console.log('失败');
}).then(value=>{
//处理成功情况
console.log('成功2');
},reason=>{
//处理失败情况
console.log('失败');
})//成功1 成功2
)
promise的单一状态与状态中转
Promise 的三种状态
- pending(进行中):初始状态,还没有结果。
- fulfilled(已成功):调用 resolve 后,代表操作成功。
- rejected(已失败):调用 reject 后,代表操作失败。
- 状态一旦变为 fulfilled 或 rejected 就不可逆。
状态可以决定回调走向
- Promise 的状态决定了后续是走 then(成功)还是 catch(失败)。
- resolve 的值会作为第一个 then 的参数传递。
- 每个 then 的返回值会作为下一个 then 的参数。
状态中转机制
- 如果 resolve 里传入另一个 Promise(比如 resolve(p1)),当前 Promise 的状态和结果会跟随 p1。
- 这样 Promise 的状态和结果就像链条一样传递。
- 如果 then 没有 return,默认返回 undefined,后续 then 拿到的就是 undefined。
new Promise((resolve,reject)=>{
resolve('买可乐');
// reject('买不起')
//两者只能生效一个
}).then(value=>{
console.log(value); //接受成功的值
},reason=>{
console.log(reason);//返回失败的原因
})//如果没有处理 就会继续向下传递
Promise的链式调用与状态传递
- Promise链式调用:每个
.then()方法都会返回一个新的Promise对象,这就是为什么可以连续调用.then()的原因。 - Promise状态传递:第一个Promise(p1)被拒绝(reject),所以第一个
.then()会执行失败回调,输出"失败"。第二个.then()接收到的是前一个Promise的结果,由于前面的then没有显式返回值,在失败情况下会传递undefined,因此第二个then也会执行失败回调,输出"失败2"。 - 事件循环机制:
- 同步代码执行完毕后,微任务(Promise回调)会优先于宏任务(setTimeout)执行
let p1=new Promise((resolve,reject)=>{
// resolve('买可乐');
reject('买不起')
})
let p2=p1.then(value=>{
console.log('成功');
},reason=>{
console.log('失败');
}).then(value=>{
console.log('成功2');
},reason=>{
console.log('失败2');
})
/* console.log(p1);
console.log(p2);//<pending> 等待状态 */
setTimeout(() => {
console.log(p1);
console.log(p2);//undefined p2拿到了p1的状态 执行了then
// 0秒后 状态已经改变 所以p2的值是undefined
}, 0);
then作为返回值的技巧
- 普通值的传递:
- 当
.then()回调函数返回普通值时,这个值会传递给下一个.then()的成功回调函数
- 当
- Promise对象的传递:
- 当
.then()回调函数返回一个Promise对象时,下一个.then()会等待这个Promise的结果 - 下一个
.then()的状态取决于返回的Promise状态
- 当
- 未显式返回的情况:
- 如果在
.then()中创建但未显式返回Promise对象,默认会返回一个resolved状态的Promise,值为undefined - 这会导致下一个
.then()接收到undefined值
- 如果在
let p1=new Promise((resolve,reject)=>{
resolve('fulfilled')//这是第一个resolve
// reject('rejected')
}).then(
value=>{
//如果then返回是一个普通的值 那么下一个then就会接收到
// return 'hello 第二个then'
//如果then返回的是一个promise 那么下一个then就会等待这个promise的结果
/* return new Promise((resolve,reject)=>{
// resolve('hello 第二个then 我是正确处理')
reject('hello 第二个then 我是错误处理')
}) */
//如果不返回 直接在then中写一个新的promise 那么下一个then就会等待这个promise的结果 如果这个promise没有处理正确或者错误 就会报错
new Promise((resolve,reject)=>{
// resolve('hello 第二个then 我是正确处理')
reject('hello 第二个then 我是错误处理')
}).then(
value=>{console.log(value);},
reason=>{console.log(reason);}
)
/* hello 第二个then 我是错误处理
undefined 这个undefined是下面的then的结果 因为对于第一个then他本就是一个新的promise 但是这个promise没有return 也就是没有上传结果但注意(这里是默认resolve的) 就会出现undefined */
},
reason=>console.log('error '+reason)
).then(
value=>{console.log('第二个then'+value);},
reason=>{
console.log(reason);
}
)
Promise错误检测与catch
错误产生的几种方式
- 显式调用
reject()传递错误 - 代码执行过程中发生异常(如注释中的
jd+1会导致引用错误)
错误处理的两种方式
方式一:使用.then()的第二个参数处理错误
.then( value => { /* 成功处理 */ }, reason => { /* 错误处理 */ } )
方式二:使用专门的.catch()方法处理错误(推荐)
.then(value => { /* 成功处理 */ }) .catch(reason => { /* 错误处理 */ })
错误传播机制
- 错误会沿着Promise链向下传播,直到被处理
- 如果中间某个环节产生了错误但未处理,会一直传递到最后的
.catch()
new Promise((resolve,reject)=>{
resolve('fulfilled') // 初始状态是成功
})
.then(value=>{
// 在这个then中创建了一个被拒绝的Promise
return new Promise((resolve,reject)=>{
reject(new Error("promsie错误"))
}).then(/* 内部处理 */)
})
.then(value=>{
console.log(value); // 如果前面的错误被处理了,这里会执行
})
.catch(reason=>{
console.log(reason); // 捕获未被处理的错误
})
Promise.resolve
Promise.resolve() 是 Promise 构造函数的一个静态方法,用于创建一个立即 resolved(成功状态)的 Promise 对象。简化了将普通值转化为Promise对象的过程
主要用途
- 创建立即成功的 Promise
// 创建一个立即 resolved 的 Promise,值为 1
Promise.resolve(1).then(value => {
console.log(value); // 输出: 1
});
- 将普通值包装成 Promise
// 将普通值转换为 Promise 对象
const value = "hello";
Promise.resolve(value).then(result => {
console.log(result); // 输出: hello
});
- 缓存机制中的应用
function query(url) {
const cache = query.cache || (query.cache = new Map());
if (cache.has(url)) {
// 如果有缓存,直接返回 resolved 状态的 Promise
console.log('缓存中');
return Promise.resolve(cache.get(url));
}
// 如果没有缓存,执行实际请求
return ajax(url).then(value => {
cache.set(url, value); // 存储到缓存
console.log('缓存中没有');
return value;
});
}
Promise.reject
Promise.reject() 是 Promise 构造函数的一个静态方法,用于创建一个立即 rejected(失败状态)的 Promise 对象。与 Promise.resolve() 相反,它创建的是一个立即失败的 Promise。
1.创建立即失败的 Promise
// 创建一个立即 rejected 的 Promise,原因是一个错误字符串
Promise.reject('fail').then(
value => {
console.log(value); // 不会执行
},
reason => {
console.log(reason); // 输出: fail
}
);
2.处理特殊对象参数
let hd = {
then(resolve, reject) {
resolve(1);
}
};
// 即使传入的对象有 then 方法,Promise.reject 仍然会创建一个 rejected 状态的 Promise
// 但这个对象会被作为 rejection reason 传递给 then 的失败回调
Promise.reject(hd).then(
value => {
console.log(value); // 会执行,输出: 1 (因为 hd.then 中调用了 resolve(1))
}
);
3.在错误处理中的应用
new Promise((resolve, reject) => {
resolve(2);
}).then(value => {
// 在某些条件下手动触发错误
if (value != 1) {
// 返回一个 rejected 状态的 Promise
return Promise.reject('参数错误');
}
}).catch(res => {
console.log(res); // 输出: 参数错误
});
Promise.all
Promise.all() 是 Promise 构造函数的一个静态方法,用于将多个 Promise 对象合并为一个 Promise 对象。只有当所有传入的 Promise 都成功时,返回的 Promise 才会成功;只要有一个 Promise 失败,返回的 Promise 就会失败
1.并行处理多个异步操作
//Promise.all
//批量处理
const p1 = new Promise((resolve, reject) => {
setTimeout(() => {
resolve("成功1");
// reject("失败1");
}, 1000);
})
const p2 = new Promise((resolve, reject) => {
setTimeout(() => {
resolve("成功2");
}, 2000);
})
const p3 = new Promise((resolve, reject) => {
setTimeout(() => {
resolve("成功3");
// reject("失败3")
}, 3000);
})
// 使用 Promise.all 等待所有 Promise 完成
Promise.all([p1, p2, p3]).then(res => {
console.log(res); // ["成功1", "成功2", "成功3"]
}).catch(err => {
console.log(err); // 如果任何一个失败,会在这里捕获
})
2.保持结果顺序
Promise.all() 返回的结果数组顺序与传入的 Promise 数组顺序一致,即使某些 Promise 先完成。
3.在 async/await中的使用
async function fn(){
let p1 = new Promise((resolve,reject)=>{
setTimeout(()=>{
resolve('p1')
},1000)
})
let p2 = new Promise((resolve,reject)=>{
setTimeout(()=>{
resolve('p2')
},1000)
})
// 并行执行
let res = await Promise.all([p1,p2])
console.log(res); // ["p1", "p2"]
}
Promise.allSettle
不管成功失败都会执行 但是返回的是一个数组 数组中每个元素是一个对象 每个对象有一个status属性 表示状态 一个value属性 表示值
[{status: "rejected", reason: "失败1"}, {status: "fulfilled", value: "成功2"}]
const p1=new Promise((resolve,reject)=>{
reject("失败1")
})
const p2=new Promise((resolve,reject)=>{
resolve("成功2")
})
Promise.allSettled([p1,p2]).then(res=>{
console.log(res);
})//不管成功失败都会执行 但是返回的是一个数组 数组中每个元素是一个对象 每个对象有一个status属性 表示状态 一个value属性 表示值
Promise.race
作用是返回一个promise对象 这个promise对象的状态是由第一个完成的promise对象决定的 如果第一个完成的promise对象是成功的 那么这个promise对象就是成功的 如果第一个完成的promise对象是失败的 那么这个promise对象就是失败的(简单说就是谁最先响应返回谁 返回结果的状态就是谁的状态)
const p1 = new Promise((resolve, reject) => {
setTimeout(() => {
resolve("成功1");
// reject("失败1");
}, 1000);
})
const p2 = new Promise((resolve, reject) => {
setTimeout(() => {
resolve("成功2");
}, 2000);
})
const p3 = new Promise((resolve, reject) => {
setTimeout(() => {
resolve("成功3");
// reject("失败3")
}, 3000);
})
Promise.race([p1,p2,p3]).then(value=>{
console.log(value);
})
async/await语法糖
async await 是promise的语法糖 类似于class简化了promise的使用
基本使用
async默认返回的状态是成功,如果需要返回reject状态需要手动抛出错误,相当于new Promise((resolve)=>{})
async function fn(){
//默认状态是resolve的
return 1;
//如果想返回reject的状态 可以使用throw new Error('错误') 只能自己手动抛出
}//类似等于new Promise((resolve)=>{})
fn().then((val)=>{
console.log(val);
})
await 就是then的简写 虽然运行逻辑看着跟同步任务一样 但本质上就是异步任务,await就是一个默认返回resolve的then
async function fn2() {
// let a = await '我是返回'
let a = await new Promise((resolve)=>{
setTimeout(() => {
resolve('fullfild')
}, 1000);
})
let site=await new Promise((resolve)=>{
setTimeout(() => {
resolve('fullfild')
}, 1000);
})
console.log(site);//一个await就是一个默认返回resolve的then 就相当于连续在一个promise上不断使用then方法
}
就是async函数就像一条河一样向下运行,然后每一个await就是一个阀门 如果await的结果不是成功就不会打开这个阀门 async的运行就会被截停 然后每个任务都是一条河 他们是否停止运行都不会影响主程序里的同步任务的运行,除非你用 try...catch 给它修好或者疏通一下。
错误处理
在async中错误处理哈市建议使用try-catch的形式进行.
//也可以使用.catch获取错误
async function fn(){
throw new Error('error')
}
fn().catch(e=>{
console.log(e);
})
//一般定义的await后面都是promise对象
async function fn(){
try {
await new Promise((resolve,reject)=>{
reject('error')
})
}catch(e){
console.log(e);
}
console.log("catch处理后正常处理");
}
fn()
awati并行执行
let p1=new Promise((resolve,reject)=>{
setTimeout(()=>{
resolve('p1')
},1000)
})
let p2=new Promise((resolve,reject)=>{
setTimeout(()=>{
resolve('p2')
},1000)
})
async function fn(){
//这里的两个await是并行执行的 不是串行执行的 是同时进行的
let res=await Promise.all([p1,p2])
console.log(res);
}
fn()
宏任务与微任务
宏任务:setTimeout,setInterval,setImmediate,requestAnimationFrame,DOM交互事件
微任务:promise.then,catch,finally,process.nextTick
执行顺序: 宏任务<DOM<微任务
在计时器中 时间越短的越先执行
setTimeout(()=>{
console.log('我是宏任务');
})//定时器属于异步任务的宏任务队列
//定时器任务的执行顺序和定时器的时间有关
//时间短的先执行
setTimeout(() => {
console.log('定时器1');
}, 2000);
setTimeout(() => {
console.log('定时器2');
}, 1000);
代码示例
console.log("1") //第一个输出
async function async1(){ //async /await 相当于new Promise,构造函数属于同步代码
await async2()
console.log("2"); !!!第一个进入微任务队列
}
async function async2(){
console.log("3"); //第二个输出
}
async1()
setTimeout(function(){ !!!第一个进入宏任务队列
console.log("4");
},0)
new Promise((resolve)=>{
console.log("5"); //第三个输出
resolve()
})
.then(()=>{ !!!第二个进入微任务队列
console.log("6");
})
.then(()=>{ !!!第三个进入微任务队列
console.log("7")
})
console.log("8"); //第四个输出end
输出结果
1
3
5
8
2
6
7
4
W3C最新标准
W3C 及 WHATWG 在 2023 年后的更新标准中,确实弃用了 “宏任务” 这一统一分类,转而用多队列分级机制替代传统的 “宏任务 / 微任务” 二分法。下面为你详细说明这一变化的原因和新标准的核心逻辑:
-
弃用原因
早期 “宏任务” 是开发者对
setTimeout、DOM事件、网络请求等异步任务的笼统归类。但随着浏览器功能逐渐复杂,不同异步任务的调度需求差异越来越大(比如用户点击交互需要优先响应,而定时器回调可稍延后),简单的 “宏任务” 分类已无法满足精细化调度的需求,所以W3C新标准放弃了这一概念。 -
新标准的核心机制
机制要点 具体说明 任务按类型分队列 不再将非微任务的异步任务统一归为宏任务,而是按任务来源细分为多个独立队列,比如存放 setTimeout回调的延时队列、处理点击 / 输入等的交互队列、处理网络请求回调的网络队列等,同类型任务会放入对应队列。队列优先级动态调度 新标准没有强制规定所有队列的固定优先级,而是允许浏览器根据实际场景动态调整。比如 Chrome 中交互队列优先级高于延时队列和网络队列,以此优先响应用户操作,提升体验。 微队列仍保持最高优先级 “微任务” 概念被保留,且微队列优先级始终最高。每次事件循环中,必须先清空微队列中的所有任务(如 Promise.then、queueMicrotask、MutationObserver回调等),之后浏览器再从其他队列中选取任务执行。 -
新旧模型对比示例
以
setTimeout(原宏任务)和Promise.then(微任务)、用户点击事件(原宏任务)为例:- 旧模型:三者会被简单划分为 “微任务(仅
Promise.then)” 和 “宏任务(另外两个)”,执行完一个宏任务后清空微队列,再执行下一个宏任务。 - 新标准模型:
Promise.then进入微队列,setTimeout回调进入延时队列,点击事件回调进入交互队列。执行时先清空微队列,之后浏览器可能优先执行交互队列的点击回调,再执行延时队列的setTimeout回调,以此保障用户交互的流畅性。
- 旧模型:三者会被简单划分为 “微任务(仅
这种变革让浏览器的异步调度更灵活,能更好适配复杂的 Web 场景,不过对开发者来说,核心记住 “微任务优先执行”,以及不同类型异步任务可能存在调度优先级差异即可。