JavaScript
作为一门 单线程语言
,同一时间只能执行一个操作。这就意味着,在处理一些耗时操作时,程序会出现 阻塞
,导致 UI 无响应。为了避免这种情况, 异步编程
变得至关重要。 异步操作
可以将耗时的任务交给浏览器或运行时环境来处理,同时保持 UI 的响应性。
回调函数
是处理异步操作的最早方法之一。它在处理事件、定时器、网络请求等方面得到了广泛应用。然而,随着异步操作嵌套层次的增加, 回调地狱(Callback Hell)
成为了一个普遍存在的问题,降低了代码的 可读性
和 可维护性
。
function fetchData(url, callback) {
fetch(url)
.then((response) => response.json())
.then((data) => callback(data))
.catch((error) => console.error(error))
}
fetchData('https://api.github.com/users/jiohon', function (data) {
console.log(data)
})
为了解决回调地狱问题, ES6
引入了 Promise
,它提供了一种更优雅的处理异步操作的方式。 Promise
允许我们将异步操作组合成 链式调用
,通过 .then()
来处理成功和失败的情况,使得代码更具结构性。同时,通过 .catch()
可以捕获链中任何位置发生的错误,使错误处理变得更方便。
function fetchData(url) {
return fetch(url)
.then((response) => response.json())
.catch((error) => console.error(error))
}
fetchData('https://api.github.com/users/jiohon')
.then((data) => console.log(data))
.catch((error) => console.error(error))
ES2017
引入了 async/await
语法。使用 async
关键字可以标记一个函数为 异步函数
,而使用 await
关键字可以等待一个异步操作完成。
async function fetchData(url) {
try {
const response = await fetch(url)
const data = await response.json()
return data
} catch (error) {
console.error(error)
}
}
;(async () => {
const data = await fetchData('https://api.github.com/users/jiohon')
console.log(data)
})()
在异步操作中。 Promise
提供了错误传播机制,可以通过 .catch()
来捕获和处理错误。同时, async/await
也可以使用 try/catch
来捕获异步操作中的异常。良好
async function fetchData(url) {
try {
const response = await fetch(url)
const data = await response.json()
return data
} catch (error) {
console.error(error)
}
}
;(async () => {
try {
const data = await fetchData('https://api.github.com/users/jiohon')
console.log(data)
} catch (error) {
console.error(error)
}
})()
getUser
,传入用户标识后,查找该用户信息,并且返回用户名。function getName() {
const res = getUser('jiohon')
return res.name
}
用户的信息
是保存在服务器中的。所以,为了获取该值,我们需要发起 异步请求
。function getUser(user) {
return fetch(`https://api.github.com/users/${user}`)
}
async function getName() {
const res = await getUser('jiohon')
const user = await res.json()
return user.name
}
async function main() {
const res = await getName()
console.log(res) // hushhhh
}
main()
但是,
async await
是有传染性
的,当一个函数变为async
后,这意味着调用他的函数也需要是async
,这破坏了getName
的同步特性。
消除异步的 传染性
是指在 JavaScript 中处理异步代码时,防止异步操作在代码中传播,影响到其他部分的执行。
利用 try...catch
并通过缓存的方式来处理异步请求结果,从而在后续的调用中直接使用缓存的数据。
cache
对象,它用于存储异步请求的状态和值。初始状态为 'pending'
,值为 null
。window.fetch
方法保存在 oldFetch
变量中,以便后面可以还原。window.fetch
方法替换为一个新的函数。在新的 fetch
方法中,首先判断 cache
的状态,如果已经有缓存的数据,则直接返回缓存的值。如果 cache
状态为 'rejected'
,则抛出缓存的错误值。'pending'
,则调用原 fetch
方法发起请求。接着在 成功
和 失败
的情况下分别将结果保存到 cache
中,并改变 cache
的状态为 'fulfilled'
或 'rejected'
。p
变量,它是新的 fetch
方法返回的 Promise
对象。这个步骤的目的是为了在 func
执行的过程中捕获到这个 Promise
,以便在后续的错误处理中使用。try
块来执行传入的 func
函数。Promise
对象。使用 .then()
方法再次执行 func
,无论是 成功
还是 失败
。最后,无论如何,都会通过 .finally()
来还原原始的 window.fetch
方法。function run(func) {
const cache = {
status: 'pending',
value: null,
}
const oldFetch = window.fetch
// 修改请求逻辑
window.fetch = function (...args) {
// 判断是否有缓存
if (cache.status === 'fulfilled') {
return cache.value
} else if (cache.status === 'rejected') {
throw cache.value
}
// 发起请求,then或catch后保存数据
const p = oldFetch(...args)
.then((res) => res.json())
// 保存值到缓存,并改变状态
.then(
(res) => {
cache.status = 'fulfilled'
cache.value = res
},
(err) => {
cache.status = 'rejected'
cache.value = err
}
)
// 抛出错误,返回当前的promise
throw p
}
try {
func()
} catch (err) {
// 捕获到抛出的primise ,判断是否为promise
if (err instanceof Promise) {
// 再次执行
err.then(func, func).finally(() => {
// 还原fetch
window.fetch = oldFetch
})
}
}
}
// run(main)