简介
JavaScript 中的异步编程是前端开发中不可或缺的一部分。异步编程的主要目标是避免阻塞主线程,保证应用的流畅性和响应性。JavaScript 采用了几种不同的方式来实现异步编程,其中最常见的包括 Promise、async/await 和事件循环机制
JavaScript 是单线程语言,这意味着它一次只能处理一个任务。为了避免长时间的阻塞,JavaScript 引入了异步编程,允许程序在执行耗时操作(如网络请求、文件操作等)时,不会阻塞主线程,保持 UI 响应性
事件循环
事件循环(Event Loop)是一种用于处理和调度事件的机制,广泛应用于异步编程模型中。它通常在单线程环境中运行,通过不断地检查和处理事件队列来实现并发任务的顺序执行
事件循环的核心思想是将并发任务转化为顺序任务,通过轮询和非阻塞方式来解决并发问题。这种方式可以提高程序的响应性和吞吐量,并减少系统资源的浪费。事件循环主要由两个组件构成:事件队列和事件处理器。事件队列是一个先进先出(FIFO)的数据结构,用于存储待处理的事件;而事件处理器则负责从队列中取出事件并执行相应的处理逻辑
在JavaScript中,事件循环是其运行时环境处理异步操作的核心机制。它允许JavaScript在不阻塞单线程执行的情况下,响应用户交互、处理网络请求、定时器回调等异步事件。事件循环的工作流程包括以下几个步骤:
- 执行同步代码:JavaScript引擎首先执行同步代码,按照顺序执行函数调用和表达式求值,直到遇到异步操作或事件
- 检查异步任务:当遇到异步操作或事件时,它们将被放置在相应的任务队列中,而不会立即执行
- 处理宏任务和微任务:事件循环从宏任务队列中取出一个任务执行,然后检查微任务队列,执行所有微任务。这个过程会一直重复下去,直到所有队列都为空
在每个宏任务执行完毕后,事件循环会再次检查微任务队列,并执行其中的所有任务。这个过程确保了高效的异步处理。理解事件循环的工作原理有助于编写高性能和响应迅速的JavaScript应用程序
总之,事件循环是处理持续运行程序中事件的关键机制,其设计和实现对于优化程序性能和适应不同应用场景至关重要
宏任务
宏任务(MacroTask)是指那些独立、离散的工作单元,它们通常包括以下几种类型:
- script标签中的脚本执行
- setTimeout、setInterval
- 网络请求(如Ajax)
- 文件操作
- UI渲染等
微任务
微任务(MicroTask)是指那些更小的任务,它们通常包括以下几种类型:
- Promise.then 、.catch、.finally
- process.nextTick (Node.js 环境)
- MutationObserver等
微任务总是先于宏任务执行
事件循环的工作流程可以总结为以下步骤:
- 执行栈选择最先进入队列的宏任务(通常是整体代码),如果有则执行
- 检查是否存在微任务,如果存在则不停地执行,直至清空微任务队列
- 更新渲染(每一次事件循环,浏览器都可能会去更新渲染)
- 重复以上步骤,直到所有宏任务和微任务都已执行完毕
Promise
Promise 是 JavaScript 中用于处理异步操作的一种机制,它代表一个可能还未完成的操作,并且提供了 then 和 catch 方法来处理成功和失败的回调,摆脱了传统异步编程的回调地狱
Promise三种状态:
- Pending(进行中):初始状态,操作未完成
- Fulfilled(已成功):操作成功完成
- Rejected(已失败):操作失败
只有异步操作的结果,可以决定当前是哪一种状态,任何其他操作都无法改变这个状态。这也是Promise这个名字的由来,它的英语意思就是“承诺”,表示其他手段无法改变
promise是同步还是异步?
Promise 是用来管理异步编程的,它本身不是异步的 ,promise是同步代码,示例:
let promise = new Promise((resolve, reject) => {
console.log(1);
})
console.log(2);
console.log(promise);
// 1
// 2
// Promise {<pending>}
promise的回调then,catch是异步,示例:
let promise = new Promise((resolve, reject) => {
console.log(1);
resolve()
})
promise.then(function() {
console.log('resolved.');
});
console.log(2);
// 1 2 resolved
Promise新建后立即执行,所以首先输出的是1,其次2。然后,then方法指定的回调函数,将在当前脚本所有同步任务执行完才会执行,所以resolved最后输出
用法
ES6 规定,Promise对象是一个构造函数,用来生成Promise实例
// 基本语法
const promise = new Promise((resolve, reject) => {
// 异步操作
if (/*成功*/) {
resolve(value);
} else {
reject(error);
}
});
// 链式调用
promise
.then(value => {
// 处理成功
})
.catch(error => {
// 处理错误
})
.finally(() => {
// 总是执行
});
// Promise方法
Promise.all([p1, p2, p3]) // 所有Promise都完成
Promise.race([p1, p2, p3]) // 第一个完成的Promise
Promise.allSettled([p1, p2, p3]) // 所有Promise都结束
Promise.any([p1, p2, p3]) // 第一个成功的Promise
Promise构造函数接受一个函数作为参数,该函数的两个参数分别是resolve和reject。它们是两个函数,由 JavaScript 引擎提供,不用自己部署
- resolve函数的作用是,将Promise对象的状态从“未完成”变为“成功”(即从 pending 变为 resolved),在异步操作成功时调用,并将异步操作的结果,作为参数传递出去
- reject函数的作用是,将Promise对象的状态从“未完成”变为“失败”(即从 pending 变为 rejected),在异步操作失败时调用,并将异步操作报出的错误,作为参数传递出去
- Promise.all()方法用于将多个 Promise 实例,包装成一个新的 Promise 实例
- Promise.race()方法同样是将多个 Promise 实例,包装成一个新的 Promise 实例,率先改变的 Promise 实例的返回值则传递给回调函数
async/await
async用于申明一个function是异步的;而await则可以认为是 async await的简写形式,是等待一个异步方法执行完成的。
它是消灭异步回调的终极武器
;async/await 是基于 Promise 的语法糖,它让异步代码看起来像同步代码,也就是用同步的写法写异步的代码,使得代码更加简洁易读
相较于Promise,async/await有何优势?
- 同步化代码的阅读体验(Promise 虽然摆脱了回调地狱,但 then 链式调⽤的阅读负担还是存在的)
- 和同步代码更一致的错误处理方式( async/await 可以⽤成熟的 try/catch 做处理,比 Promise 的错误捕获更简洁直观)
- 同步代码和异步代码可以一起编写,调试时的阅读性, 也相对更友好
- Promise并不排斥,两者相辅相成,await 相当于 Promise 的 then ,then指的是成功,不指失败
使用规则
- async 表示这是一个async函数, await只能用在async函数里面,不能单独使用
- async 返回的是一个Promise对象,await就是等待这个promise的返回结果后,再继续执行
- await 等待的是一个Promise对象,后面必须跟一个Promise对象,但是不必写then(),直接就可以得到返回值
- 异步async 调用和普通函数的使用方式一样
// 基本语法
async function getData() {
try {
const result = await fetchData();
return result;
} catch (error) {
console.error(error);
}
}
// 并行执行
async function parallel() {
const [result1, result2] = await Promise.all([
fetch('url1'),
fetch('url2')
]);
}
// 循环中使用
async function processArray(array) {
// 串行执行
for (const item of array) {
await processItem(item);
}
// 并行执行
const promises = array.map(item => processItem(item));
await Promise.all(promises);
}
示例解析
示例1
// 事件循环示例
console.log('1'); // 同步
setTimeout(() => {
console.log('2'); // 宏任务1
Promise.resolve().then(() => {
console.log('3'); // 宏任务1中的微任务
});
}, 0);
Promise.resolve().then(() => {
console.log('4'); // 微任务1
setTimeout(() => {
console.log('5'); // 微任务1中的宏任务
}, 0);
});
console.log('6'); // 同步
// 输出:1, 6, 4, 2, 3, 5
示例2
function timeout(){
return new Promise(resolve=>{
setTimeout(()=>{
console.log(1)
resolve()//成功态
})
})
}
//情况一
async function fn(){
timeout()
console.log(2)
}
fn() //打印出 2 一秒后再打印 1
//情况2
async function fn(){
await timeout()
console.log(2)
}
fn() //1秒后打印出 1 2
示例3
function timeout(){
return new Promise(resolve=>{
setTimeout(()=>{
resolve(1)//成功态,传递参数1
})
})
}
async function fn(){
const res = await timeout() //需要一个变量来接收await return的参数
console.log(res)
console.log(2)
}
fn() //1秒后打印出 1 2