JavaScript - 异步操作和异步传染性
JavaScript
作为一门单线程语言
,同一时间只能执行一个操作。这就意味着,在处理一些耗时操作时,程序会出现阻塞
,导致UI
无响应。为了避免这种情况,异步编程
变得至关重要。异步操作
可以将耗时的任务交给浏览器或运行时环境来处理,同时保持UI
的响应性。
回调函数
回调函数
是处理异步操作的最早方法之一。它在处理事件、定时器、网络请求等方面得到了广泛应用。然而,随着异步操作嵌套层次的增加,回调地狱(Callback Hell)
成为了一个普遍存在的问题,降低了代码的可读性
和可维护性
。
js
1234567891011// 使用回调函数处理异步操作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/hushed3', function (data) { console.log(data)})
Promise 和异步操作链
为了解决回调地狱问题,ES6
引入了Promise
,它提供了一种更优雅的处理异步操作的方式。Promise
允许我们将异步操作组合成链式调用
,通过.then()
来处理成功和失败的情况,使得代码更具结构性。同时,通过.catch()
可以捕获链中任何位置发生的错误,使错误处理变得更方便。
js
12345678910// 使用Promise处理异步操作function fetchData(url) { return fetch(url) .then((response) => response.json()) .catch((error) => console.error(error))}
fetchData('https://api.github.com/users/hushed3') .then((data) => console.log(data)) .catch((error) => console.error(error))
async/await
ES2017
引入了async/await
语法。使用async
关键字可以标记一个函数为异步函数
,而使用await
关键字可以等待一个异步操作完成。
js
123456789101112131415// 使用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/hushed3') console.log(data)})()
错误处理和异常
在异步操作中。Promise
提供了错误传播机制,可以通过.catch()
来捕获和处理错误。同时,async/await
也可以使用try/catch
来捕获异步操作中的异常。良好
js
1234567891011121314// 异步操作中的错误处理fetchData('https://api.github.com/users/hushed3') .then((data) => console.log(data)) .catch((error) => console.error(error))
// 使用async/await处理错误;(async () => { try { const data = await fetchData('https://api.github.com/users/hushed3') console.log(data) } catch (error) { console.error(error) }})()
异步传染性
- 假设我们有一个函数
getUser
,传入用户标识后,查找该用户信息,并且返回用户名。
js
12345function getName() { const res = getUser('hushed3')
return res.name}
- 但是
用户的信息
是保存在服务器中的。所以,为了获取该值,我们需要发起异步请求
。
js
1234567891011121314151617function getUser(user) { return fetch(`https://api.github.com/users/${user}`)}
async function getName() { const res = await getUser('hushed3') 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
方法。
js
123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051function 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)