事出于让人工智能编写了一段通过API获取当地温度的代码
// 假设的数据获取函数
async function getTemperatureAndPressure() {
const response = await fetch('https://api.example.com/weather?location=JingShengZhen,Lingshi,XinZhong,ShanXi');
const data = await response.json();
return {
indoorTemp: data.indoorTemp,
outdoorTemp: data.outdoorTemp,
pressure: data.pressure
};
}
// 计算烧开水所需时间
function calculateBoilTime(massOfWater, initialTemp, targetTemp, power) {
const specificHeatCapacity = 4.186; // 水的比热容 (J/g°C)
const deltaT = targetTemp - initialTemp;
const timeInSeconds = (massOfWater * specificHeatCapacity * deltaT) / power;
return timeInSeconds;
}
// 主程序
async function main() {
try {
const { indoorTemp, outdoorTemp, pressure } = await getTemperatureAndPressure();
console.log(`室内温度: ${indoorTemp}°C`);
console.log(`室外温度: ${outdoorTemp}°C`);
console.log(`大气压: ${pressure} Pa`);
// 假设需要烧开的水的质量是500克,目标温度是100°C
const massOfWater = 500; // 克
const targetTemp = 100; // °C
const initialTemp = indoorTemp; // 使用室内温度作为初始温度
const power = 2200; // 瓦特
const boilTime = calculateBoilTime(massOfWater, initialTemp, targetTemp, power);
console.log(`预计烧开所需时间: ${boilTime.toFixed(2)} 秒`);
} catch (error) {
console.error('Error:', error);
}
}
main();
我看到有async关键字,不知道是干啥的于是就询问了AI结果是异步编程
javascript可以通过回调函数(匿名回调函数)来实现异步编程
// 外层的 setTimeout 函数会在 3 秒后执行其回调函数。
setTimeout(() => {
console.log('等3s后');
// 内层的 setTimeout 函数会在另外的 3 秒后执行其回调函数。
setTimeout(() => {
console.log('在等3s');
}, 3000); // 等待 3000 毫秒
}, 3000); // 等待 3000 毫秒
从上面例子中可以看出可读性很低,为了解决这个问题Promise应运而生,javascript中fetch就是使用了Promise的API,fetch会返回一个Promise对象
// 第一个示例:简单的 fetch 请求
fetch('https://test123.com/1')
.then((response) => { // 当 fetch 成功执行时,会将值传给 response,并且执行回调函数
// 如果仅到这里,Promise 和 回调函数就没区别了
});
// 第二个示例:使用 Promise 链处理多个异步操作
fetch('https://test123.com/1')
.then((response) => response.json()) // 将结果转换为 JSON,成功后执行下一个操作,将值传给 json
.then((json) => console.log(json));
// 第三个示例:通过 catch 捕获异常,类似于同步编程中的 try-catch
fetch('https://test123.com/1')
.then((response) => response.json()) // 将结果转换为 JSON,成功后执行下一个操作,将值传给 json
.then((json) => console.log(json))
.catch((error) => {
console.log(error); // 有异常后后面将不再执行,并输出异常信息
})
.finally(() => { // Promise 链结束后调用 finally 方法,例如在 fetch 加载数据库信息完成后可在此停止加载动画
stopAnimation();
});
JavaScript在新标准ECMA17中加入两个关键字async,await
JavaScript 中的 async
和 await
在 JavaScript 中,async
和 await
是 ES2017(ECMAScript 2017)引入的语言特性,用于简化异步编程。
定义
- async:是一个函数修饰符,用于声明一个函数为异步函数。这样的函数会返回一个 Promise。
- await:是一个运算符,只能在
async
函数内部使用。它用于等待一个 Promise 解析或拒绝,并返回解析的结果或抛出错误。
调用方式
-
async 函数声明:
async function myAsyncFunction() { // 异步逻辑 }
-
await 运算符:
async function myAsyncFunction() { const result = await someAsyncOperation(); console.log(result); }
错误使用方法和注意事项
-
await 必须在 async 函数内部使用:
- 如果你尝试在一个普通的同步函数中使用
await
,将会导致语法错误。
- 如果你尝试在一个普通的同步函数中使用
-
async 函数总是返回一个 Promise:
- 即使没有明确返回任何值,
async
函数也会返回一个已解析的 Promise。 - 如果
async
函数中有return
语句,那么返回的 Promise 会被解析为该值。
- 即使没有明确返回任何值,
-
异常处理:
- 在
await
语句后如果发生错误,它会被捕获并在 Promise 中拒绝。 - 使用
try...catch
块来处理可能发生的错误。
async function myAsyncFunction() { try { const result = await someAsyncOperation(); console.log(result); } catch (error) { console.error('An error occurred:', error); } }
- 在
-
不要滥用 async:
- 如果你的函数不需要等待任何异步操作,最好避免使用
async
,以保持代码简洁。
- 如果你的函数不需要等待任何异步操作,最好避免使用
不要这么使用
async function f() {
const a = await fetch('一个url');
const b = await fetch('一个url');
// 虽然不存在逻辑错误,这样写会打破两个操作的并行,
// 当第一个执行完才会执行第二个操作
}
应该这么使用
async function all () {
const a = await fetch('一个url');
const b = await fetch('一个url');
const [resultA, resultB, resultC] = await Promise.all([a,b]);
//这样就会并行操作
}
const a = async (x) => {};
const b = async (x, y) => {};
const c = async (x, y, z) => {};
async function all () {
const [resultA, resultB, resultC] = await Promise.all([
a(x),
b(x, y),
c(x, y, z)
]);
console.log(resultA);
console.log(resultB);
console.log(resultC);
}
在循环中执行异步操作,不能直接掉用forEach或者map这一类方法,尽管写了await但forEach会立刻返回,不会暂停等所有异步操作执行完成,如果要暂停等所有异步操作执行完成那么就要使用for循环。这是因为 forEach
和 map
本身是同步的,它们会立即返回,而不等待异步操作完成。因此,如果您想要确保所有的异步操作都完成后再继续执行后续代码,您需要采取一些额外的措施。
错误的例子
错误使用 forEach
循环来执行异步操作的例子如下:
const userIds = [1, 2, 3, 4];
async function getUserInfo(userId) {
const response = await fetch(`https://api.example.com/users/${userId}`);
if (!response.ok) {
throw new Error(`Failed to fetch user ${userId}: ${response.statusText}`);
}
return await response.json();
}
// 错误的例子
function fetchUsersWithError() {
userIds.forEach(async userId => {
try {
const userInfo = await getUserInfo(userId);
console.log(userInfo);
} catch (error) {
console.error(error);
}
});
}
fetchUsersWithError();
在这个例子中,虽然我们使用了 async
和 await
来处理异步操作,但由于 forEach
是同步执行的,它会立即返回并继续执行后续代码,而不会等待所有的异步操作完成。这意味着 fetchUsersWithError()
函数调用完成后,所有异步操作仍在后台运行。
正确的例子
为了确保所有的异步操作都完成,我们可以使用 for...of
循环,并将整个循环包装在一个 async
函数中:
const userIds = [1, 2, 3, 4];
async function getUserInfo(userId) {
const response = await fetch(`https://api.example.com/users/${userId}`);
if (!response.ok) {
throw new Error(`Failed to fetch user ${userId}: ${response.statusText}`);
}
return await response.json();
}
// 正确的例子
async function fetchUsersCorrectly() {
try {
for (const userId of userIds) {
try {
const userInfo = await getUserInfo(userId);
console.log(userInfo);
} catch (error) {
console.error(error);
}
}
} catch (error) {
console.error('An error occurred:', error);
}
}
fetchUsersCorrectly().then(() => {
console.log('All user info has been fetched.');
}).catch(error => {
console.error('Error fetching users:', error);
});
在这个例子中,我们使用 for...of
循环遍历 userIds
数组。由于整个循环体位于一个 async
函数中,每个异步操作都会等待前一个操作完成才开始执行。这样可以确保所有的异步操作按顺序完成,并且在所有操作完成后才会继续执行后续的代码。
使用 Promise.all
或 Promise.allSettled
如果您想要并行执行所有的异步操作并等待它们全部完成,可以使用 Promise.all
或 Promise.allSettled
。
const userIds = [1, 2, 3, 4];
async function getUserInfo(userId) {
const response = await fetch(`https://api.example.com/users/${userId}`);
if (!response.ok) {
throw new Error(`Failed to fetch user ${userId}: ${response.statusText}`);
}
return await response.json();
}
// 使用 Promise.all
async function fetchUsersWithPromiseAll() {
try {
const allPromises = userIds.map(async userId => {
try {
const userInfo = await getUserInfo(userId);
return userInfo;
} catch (error) {
console.error(error);
return null;
}
});
const results = await Promise.all(allPromises);
console.log(results);
} catch (error) {
console.error('An error occurred:', error);
}
}
fetchUsersWithPromiseAll().then(() => {
console.log('All user info has been fetched.');
}).catch(error => {
console.error('Error fetching users:', error);
});
在这个例子中,我们首先创建了一个 Promise 数组,然后使用 Promise.all
来等待所有这些 Promise 都解析完毕。Promise.all
会等待所有 Promise 都解析成功,如果有任何一个 Promise 拒绝,则 Promise.all
也会立即拒绝。
请注意,Promise.all
会在任何一个 Promise 拒绝时立即拒绝,而 Promise.allSettled
则会等待所有 Promise 都解析或拒绝后返回一个包含每个 Promise 结果的对象数组。
同步编程 (Synchronous Programming)
-
定义:同步编程是一种编程模型,在这种模型下,程序的执行是顺序进行的,一个操作必须完成之后下一个操作才能开始。
-
特点:
- 等待:如果某个任务需要等待(例如 I/O 操作),整个程序都会被阻塞直到该任务完成。
- 控制流简单:由于执行路径是确定的,因此更容易理解和调试。
-
应用场景:
- 小型应用或简单的脚本。
- 对实时性要求不高的场景。
异步编程 (Asynchronous Programming)
-
定义:异步编程允许程序在等待某些操作完成的同时继续执行其他任务。
-
特点:
- 非阻塞:可以处理多个操作而无需等待任何一个操作完成。
- 复杂性增加:需要设计回调函数、事件处理程序等来处理结果。
-
应用场景:
- 高负载服务,如 Web 服务器。
- I/O 密集型应用。
- 用户交互式应用。
并行 (Parallelism)
-
定义:并行是指同时执行多个任务的能力,通常涉及到硬件层面的支持,如多核处理器或多台计算机。
-
特点:
- 提高性能:通过并行化可以显著减少总执行时间。
- 分布式计算:可以在多台计算机上执行任务。
-
应用场景:
- CPU 密集型任务,如视频渲染。
- 大数据处理和机器学习训练。
并发 (Concurrency)
-
定义:并发是指程序中多个独立的任务在同一时间段内同时进行的现象。它不一定意味着真正的并行执行。
-
特点:
- 可以在单核处理器上实现。
- 通过切换不同的任务来模拟同时执行的效果。
-
应用场景:
- 多线程应用。
- 服务器端处理多个客户端请求。
关系
- 同步与异步:同步和异步是对程序执行方式的一种描述,它们并不是相互排斥的,而是根据应用的需求来选择使用。
- 并行与并发:并行强调的是真正的同时执行多个任务,而并发则更多地关注于如何管理和调度这些任务,使得它们看起来像是同时执行的。并发可以通过并行来实现,但也可以在单个处理器上通过时间片轮转等方式实现。
- 异步与并发:异步编程是实现并发的一种手段,它使得程序能够在等待某些操作完成的同时继续执行其他任务。这可以看作是并发编程的一个子集,特别是在单处理器系统中。
- 并行与并发:并行是并发的一种特殊情况,它依赖于硬件支持来同时执行多个任务。并行编程也是实现并发的一种方法,但它更侧重于利用多核或多机环境中的资源。
总结
异步编程和并行编程都可以视为并发编程的不同方面:
- 异步编程:关注于非阻塞的程序设计,即使在单处理器环境下也能通过任务切换实现任务之间的“并发”。
- 并行编程:关注于利用多处理器或多核架构的优势来实现真正的并行执行。
两者都可以用来提高程序的性能和响应性,但在实现机制上有明显的区别。异步编程侧重于避免阻塞,而并行编程侧重于利用多处理器的硬件能力。