小熊奶糖(BearCandy)
小熊奶糖(BearCandy)
发布于 2024-08-22 / 10 阅读
0
0

同步编程,异步编程,并行,并发

事出于让人工智能编写了一段通过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 中的 asyncawait

在 JavaScript 中,asyncawait 是 ES2017(ECMAScript 2017)引入的语言特性,用于简化异步编程。

定义

  • async:是一个函数修饰符,用于声明一个函数为异步函数。这样的函数会返回一个 Promise。
  • await:是一个运算符,只能在 async 函数内部使用。它用于等待一个 Promise 解析或拒绝,并返回解析的结果或抛出错误。

调用方式

  1. async 函数声明:

    async function myAsyncFunction() {
      // 异步逻辑
    }
    
  2. 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循环。这是因为 forEachmap 本身是同步的,它们会立即返回,而不等待异步操作完成。因此,如果您想要确保所有的异步操作都完成后再继续执行后续代码,您需要采取一些额外的措施。

错误的例子

错误使用 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();

在这个例子中,虽然我们使用了 asyncawait 来处理异步操作,但由于 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.allPromise.allSettled

如果您想要并行执行所有的异步操作并等待它们全部完成,可以使用 Promise.allPromise.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)

  • 定义:并发是指程序中多个独立的任务在同一时间段内同时进行的现象。它不一定意味着真正的并行执行。

  • 特点

    • 可以在单核处理器上实现。
    • 通过切换不同的任务来模拟同时执行的效果。
  • 应用场景

    • 多线程应用。
    • 服务器端处理多个客户端请求。

关系

  • 同步与异步:同步和异步是对程序执行方式的一种描述,它们并不是相互排斥的,而是根据应用的需求来选择使用。
  • 并行与并发:并行强调的是真正的同时执行多个任务,而并发则更多地关注于如何管理和调度这些任务,使得它们看起来像是同时执行的。并发可以通过并行来实现,但也可以在单个处理器上通过时间片轮转等方式实现。
  • 异步与并发:异步编程是实现并发的一种手段,它使得程序能够在等待某些操作完成的同时继续执行其他任务。这可以看作是并发编程的一个子集,特别是在单处理器系统中。
  • 并行与并发:并行是并发的一种特殊情况,它依赖于硬件支持来同时执行多个任务。并行编程也是实现并发的一种方法,但它更侧重于利用多核或多机环境中的资源。

总结

异步编程和并行编程都可以视为并发编程的不同方面

  • 异步编程:关注于非阻塞的程序设计,即使在单处理器环境下也能通过任务切换实现任务之间的“并发”。
  • 并行编程:关注于利用多处理器或多核架构的优势来实现真正的并行执行。

两者都可以用来提高程序的性能和响应性,但在实现机制上有明显的区别。异步编程侧重于避免阻塞,而并行编程侧重于利用多处理器的硬件能力。


评论