JavaScript - 异步操作和异步传染性
前言
JavaScript
作为一门单线程语言
,同一时间只能执行一个操作。这就意味着,在处理一些耗时操作时,程序会出现阻塞
,导致 UI 无响应。为了避免这种情况,异步编程
变得至关重要。异步操作
可以将耗时的任务交给浏览器或运行时环境来处理,同时保持 UI 的响应性。
回调函数
回调函数
是处理异步操作的最早方法之一。它在处理事件、定时器、网络请求等方面得到了广泛应用。然而,随着异步操作嵌套层次的增加,回调地狱(Callback Hell)
成为了一个普遍存在的问题,降低了代码的可读性
和可维护性
。
使用回调函数处理异步操作javascript
12345678910function 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)})
Promise 和异步操作链
为了解决回调地狱问题,ES6
引入了Promise
,它提供了一种更优雅的处理异步操作的方式。Promise
允许我们将异步操作组合成链式调用
,通过.then()
来处理成功和失败的情况,使得代码更具结构性。同时,通过.catch()
可以捕获链中任何位置发生的错误,使错误处理变得更方便。
使用Promise处理异步操作javascript
123456789function 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))
async/await
ES2017
引入了async/await
语法。使用async
关键字可以标记一个函数为异步函数
,而使用await
关键字可以等待一个异步操作完成。
使用async/await处理异步操作javascript
1234567891011121314async 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/await处理错误javascript
123456789101112131415161718async 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
,传入用户标识后,查找该用户信息,并且返回用户名。
一个栗子javascript
12345function getName() {const res = getUser('jiohon')return res.name}
- 但是
用户的信息
是保存在服务器中的。所以,为了获取该值,我们需要发起异步请求
。
一个栗子javascript
1234567891011121314151617function 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
方法。
消除异步传染性javascript
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})// 抛出错误,返回当前的promisethrow p}try {func()} catch (err) {// 捕获到抛出的primise ,判断是否为promiseif (err instanceof Promise) {// 再次执行err.then(func, func).finally(() => {// 还原fetchwindow.fetch = oldFetch})}}}// run(main)