在前端开发中,与后端进行数据交互是一项非常重要的任务。今天我们将通过分析 index.html 和 ajax.html 两个文件中的代码,来深入了解前端请求的优化过程,以及如何将传统的 XMLHttpRequest(XHR)对象转换为可以使用 await 的 Promise 对象。
从 index.html 看代码优化过程
初始状态与问题
在 index.html 中,我们的目标是从 GitHub API 获取用户的仓库信息,并将其展示在页面上。一开始,我们面临几个问题:
- 页面加载时机:使用
window.onload事件会导致执行时间较晚,因为它要等到页面所有资源(包括图片等)都加载完成才会触发。 - 代码繁琐:使用
then方法进行链式调用时,代码会变得冗长且难以维护。
第一步:使用 DOMContentLoaded 事件
document.addEventListener('DOMContentLoaded', async() => {
// 代码逻辑
})
DOMContentLoaded 事件会在 HTML 文档解析完成后立即触发,而不需要等待所有资源加载完成。这样可以提前执行我们的代码,提高页面响应速度。
第二步:从 then 链式调用到 async/await
最初,我们使用 then 方法进行链式调用:
fetch('https://api.github.com/users/yourgithub').then(res => res.json()).then(data => {
document.getElementById('repos').innerHTML = data.map(item => {
`<li>${item.name}</li>`.join('')
})
})
这种方式代码比较繁琐,而且嵌套层次过多时会影响代码的可读性。我们可以使用 async/await 来优化:
const result = await fetch ('https://api.github.com/users/yourgithub/repos')
const data = await result.json()
console.log(data);
document.getElementById('repos').innerHTML =
data.map(item => `<li>${item.name}</li>`).join('');
async/await 让异步代码看起来像同步代码,提高了代码的可读性和可维护性。await 关键字会暂停当前函数的执行,直到 Promise 对象状态变为 fulfilled 或 rejected,然后返回结果。
完整优化后的代码
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>JS 请求</title>
<script>
document.addEventListener('DOMContentLoaded', async() => {
const result = await fetch ('https://api.github.com/users/yourgithub/repos')
const data = await result.json()
console.log(data);
document.getElementById('repos').innerHTML =
data.map(item => `<li>${item.name}</li>`).join('');
})
</script>
</head>
<body>
<ul id="repos">
</ul>
</body>
</html>
在 ajax.html 中将 XHR 转换为可 await 的 Promise 对象
传统 XHR 的问题
在早期,我们使用 XMLHttpRequest 对象进行接口请求。这种方式需要手动处理状态变化和错误,代码比较复杂,而且没有 Promise 的支持,难以使用现代的异步编程方式。
创建 Promise 封装 XHR
const getJSON = async function(url){
return new Promise((resolve,reject) => {
const xhr = new XMLHttpRequest();
xhr.open('GET',url)
xhr.send();
xhr.onreadystatechange = function() {
if(xhr.readyState == 4){
resolve(JSON.parse(xhr.responseText))
}
}
})
};
我们创建了一个 getJSON 函数,它返回一个 Promise 对象。在 Promise 的执行器函数中,我们实例化了一个 XMLHttpRequest 对象,打开请求并发送。然后通过 onreadystatechange 事件监听请求状态的变化,当 readyState 变为 4 时,表示响应内容已经到达,我们将响应文本解析为 JSON 并通过 resolve 方法返回。
使用 async/await 调用封装后的函数
(async () => {
const data = await getJSON('https://api.github.com/users/yourgithub/repos')
document.getElementById('repos').innerHTML =
data.map(item => `<li>${item.name}</li>`).join('');
})()
我们使用立即执行的异步函数表达式(IIFE)来调用 getJSON 函数,并使用 await 关键字等待 Promise 对象的结果。这样,我们就可以像处理同步代码一样处理异步请求。
完整代码
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Ajax</title>
</head>
<body>
<ul id="repos"></ul>
<script>
const getJSON = async function(url){
return new Promise((resolve,reject) => {
const xhr = new XMLHttpRequest();
xhr.open('GET',url)
xhr.send();
xhr.onreadystatechange = function() {
if(xhr.readyState == 4){
resolve(JSON.parse(xhr.responseText))
}
}
})
};
(async () => {
const data = await getJSON('https://api.github.com/users/yourgithub/repos')
document.getElementById('repos').innerHTML =
data.map(item => `<li>${item.name}</li>`).join('');
})()
</script>
</body>
</html>
ps: 这里立即执行函数前面记得用;。自动分号插入(ASI)的局限性: ◦ 当上一行代码以(、[、/、+、-开头时,ASI不会自动插入分号。可能导致解析器将IIFE与上一行代码合并解析。
总结
通过对 index.html 和 ajax.html 代码的分析,我们看到了前端请求代码的优化过程。从使用传统的 XMLHttpRequest 到使用现代的 fetch API,再到使用 async/await 语法糖,我们的代码变得更加简洁、易读和易于维护。同时,将 XHR 封装为 Promise 对象,让我们可以更好地使用现代异步编程方式。在实际开发中,我们应该根据项目需求和兼容性要求选择合适的请求方式。