JavaScript异步请求的Fetch API与Promise结合
JavaScript异步请求基础
在JavaScript中,异步操作是非常重要的一部分,尤其是在处理网络请求时。传统的同步请求会阻塞代码的执行,直到请求完成,这在网页应用中会导致用户界面卡顿,影响用户体验。而异步请求允许代码在等待请求响应的同时继续执行其他任务,提升了应用的流畅性和响应性。
JavaScript最初通过回调函数来处理异步操作。例如,使用XMLHttpRequest
对象发送异步请求:
const xhr = new XMLHttpRequest();
xhr.open('GET', 'https://example.com/api/data', true);
xhr.onreadystatechange = function () {
if (xhr.readyState === 4 && xhr.status === 200) {
console.log(xhr.responseText);
}
};
xhr.send();
然而,随着异步操作的增多,回调函数嵌套会变得非常复杂,形成所谓的“回调地狱”。例如:
asyncFunction1((result1) => {
asyncFunction2(result1, (result2) => {
asyncFunction3(result2, (result3) => {
// 更多嵌套...
});
});
});
为了解决回调地狱的问题,Promise
被引入。
Promise详解
Promise
是JavaScript中处理异步操作的一种更优雅的方式。一个Promise
代表一个异步操作的最终完成(或失败)及其结果值。Promise
有三种状态:
- Pending(进行中):初始状态,既不是成功,也不是失败状态。
- Fulfilled(已成功):意味着操作成功完成,此时
Promise
有一个 resolved 值。 - Rejected(已失败):意味着操作失败,此时
Promise
有一个 rejection 原因。
创建一个Promise
实例:
const myPromise = new Promise((resolve, reject) => {
// 异步操作
setTimeout(() => {
const success = true;
if (success) {
resolve('操作成功');
} else {
reject('操作失败');
}
}, 1000);
});
Promise
实例有then
、catch
和finally
方法来处理不同状态的结果。
- then方法:用于处理
Promise
成功(resolved)的情况。它接受一个回调函数作为参数,该回调函数会在Promise
变为resolved
时被调用,并且会传入Promise
的 resolved 值。
myPromise.then((value) => {
console.log(value); // 输出:操作成功
});
- catch方法:用于处理
Promise
失败(rejected)的情况。它接受一个回调函数作为参数,该回调函数会在Promise
变为rejected
时被调用,并且会传入Promise
的 rejection 原因。
myPromise.catch((error) => {
console.error(error); // 输出:操作失败
});
- finally方法:无论
Promise
是成功还是失败,都会执行finally
中的回调函数。
myPromise.finally(() => {
console.log('操作结束');
});
多个Promise
可以通过链式调用的方式进行组合,避免了回调地狱。例如:
function asyncFunction1() {
return new Promise((resolve) => {
setTimeout(() => {
resolve('结果1');
}, 1000);
});
}
function asyncFunction2(result1) {
return new Promise((resolve) => {
setTimeout(() => {
const newResult = result1 + ' -> 结果2';
resolve(newResult);
}, 1000);
});
}
function asyncFunction3(result2) {
return new Promise((resolve) => {
setTimeout(() => {
const newResult = result2 + ' -> 结果3';
resolve(newResult);
}, 1000);
});
}
asyncFunction1()
.then(asyncFunction2)
.then(asyncFunction3)
.then((finalResult) => {
console.log(finalResult); // 输出:结果1 -> 结果2 -> 结果3
})
.catch((error) => {
console.error(error);
});
Fetch API介绍
Fetch API
是JavaScript中用于发起网络请求的现代接口,它提供了一个更强大且灵活的方式来处理HTTP请求和响应。Fetch API
基于Promise
,这使得它与现有的异步操作处理方式很好地集成。
基本的fetch
请求非常简单:
fetch('https://example.com/api/data')
.then((response) => {
return response.json();
})
.then((data) => {
console.log(data);
})
.catch((error) => {
console.error('请求出错:', error);
});
在上述代码中:
fetch
函数接受一个URL作为参数,发起一个GET请求。它返回一个Promise
,该Promise
在接收到响应时被 resolved,其 resolved 值是一个Response
对象。- 第一个
then
回调函数处理Response
对象。Response
对象提供了多种方法来获取响应数据,如json()
(用于解析JSON数据)、text()
(用于获取文本数据)、blob()
(用于获取二进制大对象数据)等。这里调用response.json()
,它返回一个新的Promise
,用于解析JSON格式的响应数据。 - 第二个
then
回调函数处理解析后的JSON数据。 catch
回调函数捕获请求过程中发生的任何错误。
Fetch API的请求方法
fetch
函数默认发起GET请求,但可以通过传递一个配置对象来指定其他请求方法,如POST、PUT、DELETE等。
POST请求
const data = {
key: 'value'
};
fetch('https://example.com/api/data', {
method: 'POST',
headers: {
'Content-Type': 'application/json'
},
body: JSON.stringify(data)
})
.then((response) => {
return response.json();
})
.then((result) => {
console.log(result);
})
.catch((error) => {
console.error('请求出错:', error);
});
在上述代码中:
method
字段指定请求方法为POST
。headers
字段设置请求头,这里设置Content-Type
为application/json
,表示请求体是JSON格式的数据。body
字段包含要发送的数据,通过JSON.stringify
将JavaScript对象转换为JSON字符串。
PUT请求
const updatedData = {
key: 'newValue'
};
fetch('https://example.com/api/data', {
method: 'PUT',
headers: {
'Content-Type': 'application/json'
},
body: JSON.stringify(updatedData)
})
.then((response) => {
return response.json();
})
.then((result) => {
console.log(result);
})
.catch((error) => {
console.error('请求出错:', error);
});
DELETE请求
fetch('https://example.com/api/data', {
method: 'DELETE'
})
.then((response) => {
return response.json();
})
.then((result) => {
console.log(result);
})
.catch((error) => {
console.error('请求出错:', error);
});
Fetch API与Promise的结合优势
- 链式调用:
Fetch API
基于Promise
,可以进行链式调用,使得代码更加清晰和易于维护。例如在处理多个连续的异步请求时,能够清晰地表达请求的先后顺序和依赖关系。 - 错误处理:通过
catch
方法统一处理请求过程中的错误,避免了在每个回调函数中重复处理错误的繁琐。 - 更好的异步控制:
Promise
的特性使得可以对异步操作进行更细粒度的控制,比如使用Promise.all
和Promise.race
来处理多个异步请求。
Promise.all
Promise.all
用于并行处理多个Promise
,当所有Promise
都 resolved 时,它返回的Promise
才会 resolved。例如,同时发起多个fetch
请求:
const promise1 = fetch('https://example.com/api/data1');
const promise2 = fetch('https://example.com/api/data2');
Promise.all([promise1, promise2])
.then((responses) => {
return Promise.all(responses.map((response) => response.json()));
})
.then((dataList) => {
console.log(dataList);
})
.catch((error) => {
console.error('请求出错:', error);
});
在上述代码中:
- 首先创建了两个
fetch
请求的Promise
对象promise1
和promise2
。 - 使用
Promise.all
传入这两个Promise
数组,当这两个请求都成功响应时,Promise.all
返回的Promise
会 resolved,其 resolved 值是一个包含两个Response
对象的数组。 - 通过
map
方法对每个Response
对象调用json()
方法,并再次使用Promise.all
等待所有解析操作完成,最终得到一个包含解析后JSON数据的数组。
Promise.race
Promise.race
同样接受一个Promise
数组作为参数,但只要数组中的任何一个Promise
resolved 或 rejected,它返回的Promise
就会 resolved 或 rejected。例如:
const promise1 = fetch('https://example.com/api/data1');
const promise2 = fetch('https://example.com/api/data2');
Promise.race([promise1, promise2])
.then((response) => {
return response.json();
})
.then((data) => {
console.log(data);
})
.catch((error) => {
console.error('请求出错:', error);
});
在这个例子中,先完成的那个fetch
请求的结果会被处理,另一个请求会继续执行但不会被处理(除非需要取消)。
Fetch API的高级特性
- 请求缓存:
fetch
支持请求缓存,通过设置cache
选项来控制缓存策略。例如:
fetch('https://example.com/api/data', {
cache:'reload'
})
.then((response) => {
return response.json();
})
.then((data) => {
console.log(data);
})
.catch((error) => {
console.error('请求出错:', error);
});
cache
的取值可以是default
(默认缓存策略)、no - cache
(不使用缓存,总是从服务器获取最新数据)、reload
(重新从服务器加载数据,不使用缓存)、force - cache
(强制使用缓存,即使缓存过期)等。
- 跨域请求:
fetch
遵循同源策略,但可以通过设置mode
选项来处理跨域请求。例如:
fetch('https://otherdomain.com/api/data', {
mode: 'cors'
})
.then((response) => {
return response.json();
})
.then((data) => {
console.log(data);
})
.catch((error) => {
console.error('请求出错:', error);
});
mode
的取值可以是same - origin
(只允许同源请求)、no - cors
(允许跨域请求,但只能使用简单请求,不支持PUT
、DELETE
等方法和自定义请求头)、cors
(允许跨域请求,需要服务器设置正确的CORS头)、navigate
(用于导航请求)。
- 处理重定向:
fetch
默认会跟随重定向,但可以通过设置redirect
选项来控制。例如:
fetch('https://example.com/api/data', {
redirect: 'error'
})
.then((response) => {
return response.json();
})
.then((data) => {
console.log(data);
})
.catch((error) => {
console.error('请求出错:', error);
});
redirect
的取值可以是follow
(默认,跟随重定向)、error
(如果发生重定向,返回一个 rejected 的Promise
)、manual
(手动处理重定向,需要在Response
对象上调用response.redirected
来判断是否发生重定向,并手动处理)。
实际应用场景
- 数据获取与渲染:在网页应用中,经常需要从服务器获取数据并渲染到页面上。例如,一个博客网站需要获取文章列表数据并展示:
<!DOCTYPE html>
<html>
<head>
<meta charset="UTF - 8">
<title>博客文章列表</title>
</head>
<body>
<ul id="articleList"></ul>
<script>
fetch('https://blog.example.com/api/articles')
.then((response) => {
return response.json();
})
.then((articles) => {
const articleList = document.getElementById('articleList');
articles.forEach((article) => {
const listItem = document.createElement('li');
listItem.textContent = article.title;
articleList.appendChild(listItem);
});
})
.catch((error) => {
console.error('获取文章列表出错:', error);
});
</script>
</body>
</html>
- 表单提交:当用户提交表单时,使用
fetch
将表单数据发送到服务器进行处理。例如:
<!DOCTYPE html>
<html>
<head>
<meta charset="UTF - 8">
<title>表单提交</title>
</head>
<body>
<form id="myForm">
<label for="name">姓名:</label><input type="text" id="name" name="name"><br>
<label for="email">邮箱:</label><input type="email" id="email" name="email"><br>
<input type="submit" value="提交">
</form>
<script>
const form = document.getElementById('myForm');
form.addEventListener('submit', (event) => {
event.preventDefault();
const formData = new FormData(form);
fetch('https://example.com/api/submitForm', {
method: 'POST',
body: formData
})
.then((response) => {
return response.json();
})
.then((result) => {
console.log(result);
})
.catch((error) => {
console.error('提交表单出错:', error);
});
});
</script>
</body>
</html>
在上述代码中:
- 给表单添加了
submit
事件监听器,阻止表单默认的提交行为。 - 使用
FormData
对象来收集表单数据。 - 发起
POST
请求,将FormData
对象作为请求体发送到服务器。
注意事项
- 旧浏览器兼容性:
Fetch API
在一些旧版本浏览器中不支持,需要使用polyfill
来提供兼容性。例如,可以使用whatwg - fetch
库来实现兼容。 - 网络错误处理:虽然
catch
可以捕获请求过程中的错误,但对于一些网络相关的底层错误,如网络连接中断等,可能需要更复杂的处理机制,例如使用navigator.onLine
属性来检测网络状态。 - 响应状态码处理:
fetch
默认情况下,即使响应状态码是404、500等错误码,Promise
也不会被 rejected,需要手动检查response.status
来处理非200 - 299范围的状态码。例如:
fetch('https://example.com/api/data')
.then((response) => {
if (!response.ok) {
throw new Error('网络响应错误:'+ response.status);
}
return response.json();
})
.then((data) => {
console.log(data);
})
.catch((error) => {
console.error('请求出错:', error);
});
通过Fetch API
与Promise
的结合,JavaScript开发者能够更高效、更优雅地处理异步网络请求,构建出更强大、更流畅的网络应用。无论是简单的数据获取,还是复杂的多请求并发处理,这种组合都提供了良好的解决方案。在实际开发中,需要根据具体的业务需求和场景,合理运用这些技术,同时注意兼容性和错误处理等方面的问题。