异步编程
JavaScript 的事件循环与调用栈
1. JavaScript 的单线程模型
- JavaScript 运行在浏览器或 Node.js 环境中,它是一个单线程的语言,这意味着它一次只能执行一个任务。
- 单线程意味着不会有多个线程同时执行 JavaScript 代码,这有助于简化编程模型,但也导致了对异步编程的需求。
2. 调用栈(Call Stack)
- 调用栈是一个特殊的数据结构,用于存储函数的上下文。
- 当一个函数被调用时,它会被推入调用栈;当函数执行完毕后,它会从调用栈中弹出。
- 调用栈遵循后进先出(LIFO)的原则。
3. 事件循环(Event Loop)
- 事件循环是 JavaScript 的运行机制,它允许 JavaScript 引擎在单线程环境中处理异步操作。
- 事件循环不断的检查调用栈是否为空,如果调用栈为空,它将从任务队列中取出任务并推入调用栈执行。
- 任务队列中的任务通常是异步操作的结果,如定时器、网络请求的响应等。
4. 宏任务与微任务
- 宏任务(Macro Tasks)与微任务(Micro Tasks)是事件循环处理任务的两种类型。
- 宏任务包括:setTimeOut、setInterval、I/O、UI 渲染等。
- 微任务包括:Promise.then、Promise.catch、async/await 等。
- 微任务总是优先于宏任务执行。
回调函数与回调地狱
1. 回调函数(Callback)
- 回调函数是一种将函数作为参数传递给另一个函数的技术,以便在某个事件或条件满足时执行。
- 在异步编程中,回调函数常用语处理异步操作的完成。
2. 理解回调地狱(Callback Hell)
- 回调地狱是指多层嵌套的回调函数,这使得代码难以阅读和维护。
- 回调地狱通常发生在多个异步操作需要按顺序执行时。
3. 避免回调地狱
- 使用现代 JavaScript 的 Promise 和 async/await 可以避免回调地狱。
- 通过将异步代码转换为同步代码风格,可以提高代码的可读性和维护性。
通过 I/O 理解异步与非阻塞
在 JavaScript 中,当涉及到 I/O 操作,如网络请求或文件读写,这些操作通常涉及到与操作系统的交互。由于这些操作可能需要等待外部资源(如从服务器获取数据或从硬盘读取文件),他们可能会花费比 CPU 计算更长的时间。在传统的多线程编程中,如果这些操作使用是同步的,那么执行他们的线程可能会被阻塞,直到操作完成。但在 JavaScript 中,这些 I/O 操作被设计为异步和非阻塞的,这意味着:
- 操作系统介入:当 JavaScript 代码发起一个 I/O 请求时,这个请求会被发送到操作系统,操作系统负责管理对外部资源的访问。
- 非阻塞行为:在 JavaScript 中,一旦请求被发送,主线程(即 JavaScript 的执行线程)不会等待这个请求完成。相反,它会继续执行后续的代码。这是因为 JavaScript 运行时环境将请求委托给操作系统,并注册一个回调函数,该回调函数将在请求完成时被调用。
- 操作系统的异步处理:操作系统有能力异步处理请求。这意味着它可以同时处理多个请求,而不需要为每个请求阻塞一个线程。操作系统会在后台执行这些操作,并在操作完成时通知 JavaScript 运行时环境。
- 事件通知:当请求完成时,操作系统会通知 JavaScript 运行时环境。这时,请求的回调函数会被放入事件循环的任务队列中,等待执行。
- 回调函数执行:当事件循环的调用栈清空,且没有其他更高优先级的任务时,事件循环会从任务队列中取出回调函数并放入调用栈中执行。这时,JavaScript 代码可以处理请求的结果。
通过这种方式,JavaScript 能够在不阻塞主线程的情况下执行 I/O 操作,从而保证了程序的响应性和效率。这种模式允许 JavaScript 程序同时处理多个 I/O 操作,而不需要为每个请求分配一个单独的线程,这在单线程环境中尤为重要。
Promise
- Promise 是 ES6 新增的特性,是一个代表未来将会完成的异步操作的对象。Promise 有三种状态:Pending(进行中)、Fulfilled(已成功)、Rejected(已失败)。
- Promise 的实例是通过 new Promise 来创建 。构造函数的参数是一个执行器函数(executor function),函数将在创建后立即执行。执行器函数通常包含异步操作,并在操作完成时调用 resolve 或 reject 函数。
- Promise 通过 then、catch、finally 添加回调函数:
-
- then:.then(resolve, reject);一般用于指定 Promise 成功时的回调函数,返回一个新的 Promise。
- catch:.catch(reject);用于指定 Promise 失败时的回调函数,返回一个新的 Promise。
- finally:.finally();无论成功或失败都会调用,返回一个新的 Promise。
- Promise.all():接收一个 Promise 数组作为参数,只有当所有 Promise 都成功时,返回的 Promise 才会成功,resolve 返回的结果是所有 Promise 的结果的数组。如果传入的 Promise 有任何一个失败了,Promise.all 返回的 Promise 会立即被拒绝,拒绝的原因是第一个失败的 Promise 的拒绝值。
- Promise.race():同样接受一个 Promise 数组作参数,但只要有一个 Promise 成功或失败,将以次作为结果返回。
手写 Promise
- queueMicrotask 是一个 Web API,用途是将一个函数排入微任务队列,内置 Promise 是由次实现的。
- 手写版没有做类型检查、循环引用等处理,方法也没有全部实现,仅为学习。
class CustomPromise {
constructor(excutor) {
// 状态
this.state = 'pending'
// 值
this.value = undefined
// onFullfilled 回调数组
this.onFulfilledCallbacks = []
// onRejected 回调数组
this.onRejectedCallbacks = []
try {
// 定义时立即执行执行器函数
excutor(this._resolve, this._reject)
} catch (err) {
this._reject(err)
}
}
_resolve = (value) => {
// 模拟真实的 Promise 行为,使用 queueMicrotask 包装 _resolve 逻辑
if (this.state === 'pending') {
queueMicrotask(() => {
this.state = 'fulfilled'
this.value = value
this.onFulfilledCallbacks.forEach((fn) => fn())
})
}
}
_reject = (error) => {
if (this.state === 'pending') {
// 模拟真实的 Promise 行为,使用 queueMicrotask 包装 _reject 逻辑
queueMicrotask(() => {
this.state = 'rejected'
this.value = error
this.onRejectedCallbacks.forEach((fn) => fn())
})
}
}
then(onFulfilled, onRejected) {
// 处理默认
if (typeof onFulfilled !== 'function') {
onFulfilled = (value) => value
}
if (typeof onRejected !== 'function') {
onRejected = (error) => {
throw error
}
}
let promise = new CustomPromise((resolve, reject) => {
if (this.state === 'pending') {
// 如果是 pending,加入队列
this.onFulfilledCallbacks.push(() => {
queueMicrotask(() => {
try {
let result = onFulfilled(this.value)
resolve(result)
} catch (err) {
reject(err)
}
})
})
this.onRejectedCallbacks.push(() => {
queueMicrotask(() => {
try {
let result = onRejected(this.value)
resolve(result)
} catch (err) {
reject(err)
}
})
})
} else if (this.state === 'fulfilled') {
// fulfilled 同步结果,向后传
queueMicrotask(() => {
try {
let result = onFulfilled(this.value)
resolve(result)
} catch (err) {
reject(err)
}
})
} else if (this.state === 'rejected') {
// rejected 同步结果,向后传
queueMicrotask(() => {
try {
let result = onRejected(this.value)
resolve(result)
} catch (err) {
reject(err)
}
})
}
})
return promise
}
catch(onRejected) {
// 只有 onRejected 的语法糖
return this.then(null, onRejected)
}
finally(onFinally) {
return this.then(
(value) => CustomPromise.resolve(onFinally()).then(() => value),
(error) =>
CustomPromise.resolve(onFinally()).then(() => {
throw error
})
)
}
static resolve(value) {
if (value instanceof CustomPromise) {
return value
}
return new CustomPromise((resolve) => {
resolve(value)
})
}
static reject(error) {
return new CustomPromise((resolve, reject) => {
reject(error)
})
}
static all(promises) {
return new CustomPromise((resolve, reject) => {
const result = []
let resolvedCount = 0
if (promises.length === 0) {
resolve(result)
return
}
promises.forEach((promise, index) => {
promise.then((value) => {
result[index] = value
resolvedCount++
if (resolvedCount === promises.length) {
resolve(result)
}
}),
(error) => {
reject(error)
}
})
})
}
static race(promises) {
return new CustomPromise((resolve, reject) => {
let isResolved = false
promises.forEach((promise) => {
promise.then(
(value) => {
if (!isResolved) {
resolve(value)
isResolved = true
}
},
(error) => {
if (!isResolved) {
reject(error)
isResolved = true
}
}
)
})
})
}
}
封装可取消的 Promise
function makeCancelable(promise) {
let isCanceled = false
const wrappedPromise = new Promise((resolve, reject) => {
promise.then((val) => (isCanceled ? reject({ isCanceled, value: val }) : resolve(val)))
promise.catch((error) => (isCanceled ? reject({ isCanceled, error }) : reject(error)))
})
return {
promise: wrappedPromise,
cancel() {
isCanceled = true
}
}
}
async/await
- async 函数是使用 async 关键字声明的函数,它会返回一个 Promise 对象。任何在 async 函数中执行的一步操作都会返回一个 Promise。
- await 关键字只能在 async 函数内部使用。await 后面通常会跟着一个 Promise,它会暂停 async 函数的执行,直到 Promise 解决或拒绝。
- 在 async 函数中,可以使用 try...catch 来处理 await 调用的 Promise 可能产生的错误。
- 如果 async 函数正常执行结束,会返回一个解决状态的 Promise;如果 async 函数中抛出错误,它会返回一个拒绝状态的 Promise。
在 useEffect 中使用 async 函数
useEffect(() => {
async function fetchData() {
try {
const data = await fetchSomeData();
// 使用数据进行一些操作
} catch (error) {
console.error('Failed to fetch data:', error);
}
}
fetchData();
}, [dependency]); // 确保将依赖项数组传递给 useEffect