探索 ES6 生成器 ( Generator ) 的异步编程应用

image.png

一. 前言

在之前的文章中,我们介绍了生成器函数的基本概念和常见应用,包括异步操作的顺序执行、控制异步流程等,同时也了解到 Promise 和生成器结合的应用可以帮助我们更方便地处理异步操作。详细了解请参考之前的文章:

学习 ES6 生成器 ( Generator ) :掌握优雅的异步编程利器icon-default.png?t=O83Ahttps://blog.csdn.net/qq_24956515/article/details/142899986

然而,生成器函数的应用不仅限于此,它还有一些比较高级的应用,可以用于实现并发控制,使用生成器实现可取消的异步操作等等,以提高代码的性能和效率。在本文中,我们将介绍生成器的这些高级应用。

在介绍生成器的应用之前,我们先介绍几种生成器中的错误处理方法,在生成器中处理错误也是一种重要的机制,可以有效地捕获和处理异步操作中的错误。在接下来的异步编程应用中,我们会常常会用到如何处理异常信息。

二. 生成器中的错误处理

1. try...catch 语句

在生成器函数中处理错误可以借助于try...catch语句。通过在生成器函数中使用try...catch语句,可以捕获异步操作中的错误并采取相应的处理措施。

下面是一个示例,展示了在生成器函数中处理错误的方法:

function* asyncGenerator() {
  try {
    const result = yield asyncOperation(); // 进行异步操作

    console.log("异步操作结果:", result);
  } catch (error) {
    console.error("发生错误:", error);
  }
}

function asyncOperation() {
  return new Promise((resolve, reject) => {
    // 模拟异步操作
    setTimeout(() => {
      const success = Math.random() >= 0.5;
      if (success) {
        resolve("成功结果");
      } else {
        reject(new Error("操作失败"));
      }
    }, 1000);
  });
}

const asyncFlow = asyncGenerator();
const { value, done } = asyncFlow.next();

if (!done) {
  value
    .then((data) => {
      asyncFlow.next(data);
    })
    .catch((error) => {
      asyncFlow.throw(error);
    });
}

在上述示例中,我们定义了一个生成器函数asyncGenerator,其中使用了try...catch语句来捕获可能发生的异步操作内的错误。

asyncGenerator函数中,我们通过yield关键字将异步操作asyncOperation进行了挂起。当异步操作完成后,将结果传递给生成器函数的yield语句。

在外部,我们通过调用asyncGenerator函数并获取生成器对象后,使用next()方法将生成器函数的执行推进一步。然后,我们通过判断生成器对象的done属性来判断是否所有步骤都已完成。如果未完成,我们根据value属性是Promise对象还是错误对象来处理下一步是next()还是throw()

在上述示例中,asyncOperation函数是一个模拟异步操作的函数,它返回一个Promise对象,模拟异步操作的成功或失败。在异步操作中,resolve被调用时会传递成功结果,reject被调用时会传递错误信息。

执行结果如下图所示:

image.png

通过在生成器函数中使用try...catch语句,我们可以捕获异步操作中可能发生的错误,并在catch块中进行相应的处理。

请注意,错误处理是实现稳定的异步代码非常重要的一部分。在实际开发中,你可能还需要结合特定的异步控制库来处理错误,以确保错误能够被正确捕获和处理。

2. throw 方法

在生成器函数中,可以使用throw()方法来抛出一个错误,并使生成器函数在相应的catch语句中暂停执行。

下面是一个示例,展示了使用生成器函数的throw()方法和错误处理的方法:

function* generatorFunction() {
  try {
    yield "Step 1";
    yield "Step 2";
    throw new Error("Something went wrong"); // 抛出错误
    yield "Step 3"; // 不会执行到这里
  } catch (error) {
    console.log("Error:", error.message);
    yield "Step 4";
  }
}

const generator = generatorFunction();
console.log(generator.next().value); // Output: Step 1
console.log(generator.next().value); // Output: Step 2
console.log(generator.throw(new Error("Another error message")).value); // Output: Error: Another error message
console.log(generator.next().value); // Output: Step 4

在上述示例中,我们定义了一个生成器函数generatorFunction,其中通过yield语句定义了一系列的步骤。

generatorFunction中,我们使用了try...catch语句来捕获在生成器函数中可能发生的错误。当抛出错误时,生成器函数会立即停止执行,并跳转到相应的catch语句中处理错误。在catch语句中,我们可以对捕获的错误进行相应的处理。

在外部,我们首先创建了生成器对象generator,然后通过next()方法来逐步执行生成器函数中的步骤。在第一次调用next()之后,生成器函数会执行到第一个yield语句,并返回相应的值。

在第二次调用next()之后,生成器函数会继续执行到第二个yield语句,并返回相应的值。

接着,我们使用throw()方法抛出了一个新的错误,并将错误对象作为参数传递给throw()方法。生成器函数会立即停止执行,并跳转到相应的catch语句中处理错误。我们可以在catch语句中对捕获的错误进行相应的处理,并返回相应的值。

在上述示例中,生成器函数在抛出错误后,不会再执行剩余的步骤。通过使用throw()方法和相应的错误处理机制,我们可以更好地控制生成器函数的执行流程,并及时处理发生的错误。

执行的结果如下图所示:

image.png

3. 利用 Promise 错误处理机制

在生成器中,可以通过返回 reject 状态的 Promise 来处理错误。这种方式非常适合处理异步操作中的错误。下面是详细的说明:

function asyncOperation() {
  return new Promise((resolve, reject) => {
    // 异步操作
    setTimeout(() => {
      // 模拟执行错误操作
      const error = new Error("An error occurred");
      reject(error);

      // 模拟执行成功操作
      // resolve('Async operation completed');
    }, 2000);
  });
}

function* myGenerator() {
  try {
    const result = yield asyncOperation();
    console.log("Result:", result);
  } catch (error) {
    console.error("Error:", error);
  }
}

const generator = myGenerator();

function handlePromise(result) {
  generator.next(result);
}

function handleError(error) {
  generator.throw(error);
}

generator.next().value.then(handlePromise).catch(handleError);

在上面的代码中,asyncOperation函数返回一个 Promise 对象,模拟一个异步操作,延迟 2 秒后会进入 reject 状态。在myGenerator生成器函数中,我们使用yield asyncOperation()来暂停生成器,并返回 Promise 对象。通过try-catch语句块,我们可以对错误进行捕获和处理。如果 Promise 对象进入了 reject 状态,错误会被抛到catch块中进行处理。

在主程序中,通过调用生成器的next()方法,启动生成器的执行。在 Promise 的then方法中,调用handlePromise函数将结果传递给生成器继续执行。如果 Promise 进入了 reject 状态,错误会被捕获并抛出,从而触发生成器的catch块。

image.png

通过这种方式,我们可以在生成器中实现错误处理逻辑,将异步操作的错误捕获和处理放在生成器内部,使得代码更加简洁和易于阅读。同时,结合 Promise 的错误处理机制,可以保证异步操作的错误能够得到正确地处理和传递。

需要注意的是,在处理 Promise 对象时,可以使用 .then 方法获取异步操作的结果,并通过 generator.next()方法将结果返回给生成器。如果 Promise 进入 reject 状态,可以通过 .catch 方法捕获错误并处理。通过这种方式,可以在异步操作中实现错误处理的流程控制。

总之,通过返回 reject 状态的 Promise,可以在生成器中有效地处理异步操作中的错误,并实现错误处理的流程控制。这种方式可以使生成器更加灵活和可靠。

三. 生成器的异步编程应用

1. 与 async/await 结合使用

生成器函数和async/await可以很好地结合使用,以简化异步编程,并使代码更具可读性和可维护性。下面是使用生成器和async/await模式处理异步操作的示例:

function getData() {
  return new Promise((resolve) => {
    setTimeout(() => {
      resolve("数据");
    }, 1000);
  });
}

function* fetchAsyncData() {
  try {
    const data = yield getData();
    console.log("获取到的数据:", data);

    const moreData = yield getData();
    console.log("更多的数据:", moreData);

    return "完成";
  } catch (error) {
    console.error("发生错误:", error);
    return "出错";
  }
}

async function fetchData() {
  try {
    const generator = fetchAsyncData();
    let result = generator.next();

    while (!result.done) {
      result = await result.value;
      result = generator.next(result);
    }

    console.log("最终结果:", result.value);
  } catch (error) {
    console.error("发生错误:", error);
  }
}

fetchData();

在上面的示例中,fetchAsyncData是一个生成器函数,用于处理异步操作。在生成器函数中,我们使用yield关键字暂停执行,并通过yield关键字返回一个Promise对象,此处使用了getData函数模拟异步操作。

fetchData函数中,我们使用async/await模式来驱动生成器函数的执行。在while循环中,我们使用await关键字等待异步操作的结果,并通过generator.next()方法将结果传递给生成器函数。

image.png

生成器函数中的try...catch块可以捕获异步操作中的异常情况,确保代码的稳定性。在catch块中,我们可以处理错误并返回结果通过结合使用生成器和async/await式,我们可以以顺序和分步的方式处理异步操作,使代码更清晰、易读,并且能够很好地处理异常情况。

2. 实现可取消的异步操作

使用生成器实现可取消的异步操作可以通过结合使用生成器函数和yield语句来实现暂停和恢复异步任务。下面是一个简单的示例:

function* cancellableAsyncOperation() {
  try {
    const result = yield new Promise((resolve, reject) => {
      const timeoutId = setTimeout(() => {
        resolve("操作完成");
      }, 3000);

      // 注册取消操作的回调函数
      onCancel(() => {
        clearTimeout(timeoutId);
        reject(new Error("操作被取消"));
      });
    });

    console.log(result);
  } catch (err) {
    console.error("操作出错:", err.message);
  }
}

// 取消操作的函数
let cancelCallback = null;
function onCancel(callback) {
  cancelCallback = callback;
}

// 取消操作的函数调用
function cancelOperation() {
  if (cancelCallback) {
    cancelCallback();
  }
}

// 执行异步操作
const iterator = cancellableAsyncOperation();
const promise = iterator.next().value;

promise
  .then(() => {
    console.log("操作完成");
  })
  .catch((err) => {
    console.error("操作被取消或出错:", err.message);
  });

// 5秒后取消操作
setTimeout(() => {
  console.log("取消操作");
  cancelOperation();
}, 5000);

在这个示例中,我们定义了一个生成器函数cancellableAsyncOperation,它模拟一个异步操作的执行过程。在生成器函数内部,我们创建了一个Promise对象,并在其中注册了一个定时器来模拟异步操作的延时执行。同时,我们通过onCancel函数,注册了一个取消操作的回调函数。

然后,我们定义了一个全局的cancelCallback变量,用来保存取消操作的回调函数。

接着,我们通过调用iterator.next().value来获取生成器的第一个yield表达式返回的Promise对象,并通过then()catch()方法处理操作的完成和取消或出错的情况。

最后,我们通过setTimeout函数,在 5 秒后调用cancelOperation函数来取消操作。cancelOperation函数会调用之前注册的取消操作的回调函数,从而中断异步操作的执行。

执行流程如下图所示:

record.gif

record.gif

需要注意的是,这个示例只是模拟了可取消的异步操作的实现方式。在实际使用中,可能需要根据具体需求和异步操作的性质,进行适当的调整和优化。

3. 实现并发控制

在编程中,我们经常需要处理并行执行的任务,例如同时下载多个文件,或者同时发送多个请求。并发控制就是一种管理并行任务执行的机制,它可以控制任务的数量、顺序和结果处理等。

基本原理及步骤

生成器函数提供了一种优雅且灵活的方式来实现并发控制。通过使用生成器函数和 yield 表达式,我们可以轻松地管理多个并发任务的执行,以及处理任务的结果。下面是生成器函数并发控制的基本原理:

  • 创建一个生成器函数,该函数包含多个异步任务的生成器调用,并将每个任务包装在 yield 表达式中。

  • 通过控制生成器的迭代过程,以及使用异步操作的 Promise 结果,实现任务的并发执行和结果处理。

因此,生成器的并发控制可以通过使用异步操作的 Promise 和生成器函数的特性来实现。下面是一个简单的示例,我们看一下如何使用生成器函数来管理并发任务的执行:

// 模拟异步请求
function fetch(url) {
  return new Promise((resolve, reject) => {
    console.log("异步操作执行中:" + url);

    const timer = setTimeout(() => {
      clearTimeout(timer);
      console.log("异步操作完成:" + url);
      resolve({ status: 1, msg: "异步操作完成:" + url, data: null });
    }, 2000);
  });
}

// 生成器函数
function* taskGenerator() {
  const task1 = yield fetch("https://api.example.com/task1");
  const task2 = yield fetch("https://api.example.com/task2");
  const task3 = yield fetch("https://api.example.com/task3");

  console.log(task1);
  console.log(task2);
  console.log(task3);
}

function executeGenerator(generator) {
  const iterator = generator();

  function handle(iteratorResult) {
    if (iteratorResult.done) {
      return Promise.resolve(iteratorResult.value);
    }

    return Promise.resolve(iteratorResult.value)
      .then((res) => handle(iterator.next(res)))
      .catch((err) => iterator.throw(err));
  }

  try {
    return handle(iterator.next());
  } catch (err) {
    return Promise.reject(err);
  }
}

// 执行任务
executeGenerator(taskGenerator)
  .then(() => {
    console.log("所有任务完成");
  })
  .catch((err) => {
    console.error("执行过程中出错:", err);
  });

在上面这个示例中,我们定义了一个生成器函数taskGenerator,它包含三个异步任务。每个任务使用yield语句暂停生成器的执行,等待相应的异步操作结果。

然后,我们定义了一个辅助函数executeGenerator,用于执行生成器。在函数内部,我们通过递归调用handle函数,依次处理生成器的每个yield表达式。其中,handle函数接收一个yield表达式的结果,并根据结果继续执行生成器的下一个yield表达式,直到生成器执行完毕。

最后,我们调用executeGenerator(taskGenerator)来执行生成器。在生成器执行过程中,每个任务会并发地执行。当所有任务都完成后,输出“所有任务完成”。如果在执行过程中出现错误,会捕获并输出错误信息。

执行流程如下图所示:

record.gif

record.gif

需要注意的是,生成器的并发控制是通过顺序执行任务并处理它们的结果来实现的,并非真正的并行执行。如果需要更高级的并行控制,可以结合使用生成器和其他异步编程模式,例如使用Promise.all()来同时执行多个任务。

通过以上的代码演示,我们可以实现并发任务的执行和结果处理,有效地利用生成器的并发控制能力。当然,这只是一个简单的示例,你可以根据实际需求进行更复杂的并发控制实现。

4. 嵌套和组合

生成器的嵌套和组合是指在生成器函数中使用其他生成器函数的能力,以及将多个生成器函数组合在一起以形成更复杂的生成器函数。

嵌套其他生成器函数

在生成器函数中使用其他生成器函数可以让我们更好地组织和复用代码。我们可以将一部分逻辑封装在一个生成器函数中,然后在另一个生成器函数中通过 yield* 语法来调用这个生成器函数。这样做可以减少代码的重复性,提高代码的可读性和可维护性。

下面是一个简单的例子,展示了如何在生成器函数中嵌套使用其他生成器函数:

function* innerGenerator() {
  yield 1;
  yield 2;
  yield 3;
}

function* outerGenerator() {
  yield "a";
  yield "b";
  yield "c";
  yield* innerGenerator();
  yield "d";
  yield "e";
}

const generator = outerGenerator();
for (const value of generator) {
  console.log(value);
}

在上面的例子中,innerGenerator 是一个简单的生成器函数,它生成了数字 1、2、3。outerGenerator 是另一个生成器函数,它先生成字母 'a'、'b'、'c',然后通过 yield* 调用了 innerGenerator,生成了数字 1、2、3。最后,它生成了字母 'd'、'e'。当我们遍历 outerGenerator 的返回值时,会依次输出 'a'、'b'、'c'、1、2、3、'd'、'e'。

image.png

组合多个生成器函数

除了嵌套生成器函数,我们还可以通过组合多个生成器函数来创建更复杂的生成器函数。这样做可以将不同的生成器函数的逻辑组合在一起,形成一个新的生成器函数。在组合生成器函数时,可以使用 yield* 或通过遍历多个生成器函数的返回值来实现。

下面是一个示例,演示了如何组合多个生成器函数:

function* generator1() {
  yield "A";
  yield "B";
}

function* generator2() {
  yield "1";
  yield "2";
}

function* combinedGenerator() {
  yield* generator1();
  yield* generator2();
}

const generator = combinedGenerator();
for (const value of generator) {
  console.log(value);
}

在上面的例子中,generator1generator2 分别是两个简单的生成器函数,分别生成了字母 'A'、'B' 和数字 '1'、'2'。combinedGenerator 是一个组合生成器函数,通过 yield* 调用了 generator1generator2,以达到将两者逻辑组合在一起的目的。当我们遍历 combinedGenerator 的返回值时,会依次输出 'A'、'B'、'1'、'2'。

image.png

通过生成器的嵌套和组合,我们可以更好地组织和复用生成器函数的逻辑,实现更灵活和可扩展的功能。这种技术可以在异步编程、数据处理和状态管理等方面发挥重要作用,帮助我们更高效地处理复杂的业务需求。

四. 总结

在本篇文章中,我们了解了生成器中的错误处理,如何使用生成器来实现可取消的异步操作和并发控制,以及利用嵌套和组合这些技术来应对复杂的异步场景。

通过生成器函数,我们可以使用 yield 语句暂停和恢复异步操作的执行,实现可取消的异步操作。以注册取消操作的回调函数,并在需要取消操作时调用该回调函数,以中断异步操作的执行。这样,我们就能够更加灵活地管理和控制异步操作。

此外,通过将生成器与 Promise 结合,我们能够更方便地组织和管理异步操作的流程。这样,我们可以以更清晰、易读的方式编写异步代码。

在处理复杂的异步场景时,通过嵌套和组合生成器函数,可以实现更复杂的异步操作流程,以满足特定的需求。

总的来说,ES6 生成器异步编程应用为我们提供了更多的工具和技术,帮助我们处理复杂的异步操作和错误处理。无论是取消异步操作、实现并发控制,还是嵌套和组合这些技术,我们都可以根据具体的场景和需求,选择合适的方法和技术,提高代码的可读性、可维护性和性能。通过不断探索这些新的思路,我们可以更好地、更有技巧的应对异步编程带来的挑战。

 

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:/a/890683.html

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

前端Vue3字体优化三部曲(webFont、font-spider、spa-font-spider-webpack-plugin)

前端Vue字体优化三部曲(webFont、font-spider、spa-font-spider-webpack-plugin) 引言 最近前端引入了UI给的思源黑体字体文件,但是字体文件过于庞大,会降低页面首次加载的速度,目前我的项目中需要用到如下三个字体文…

Java 8 的内存结构

Java8内存结构图 虚拟机内存与本地内存的区别 Java虚拟机在执行的时候会把管理的内存分配成不同的区域,这些区域被称为虚拟机内存,同时,对于虚拟机没有直接管理的物理内存,也有一定的利用,这些被利用却不在虚拟机内存…

每天3分钟,彻底弄懂神经网络的优化器(十)Nadam

1. Nadam算法的提出 Nadam(Nesterov-accelerated Adaptive Moment Estimation)算法是由Tim Salimans et al. 在2016年提出的。这一算法结合了Adam算法和Nesterov Accelerated Gradient(NAG)算法的优点,旨在提高优化算…

[运维]6.github 本地powershell登录及设置ssh连接

当我在本地的git hub 进行修改后,需要推送到远程github仓库。 当我运行了git add . git commit -m "ingress-controller image" 以后,运行git push origin main,发现由于网络原因无法连接到远程github仓库。 此时开始设置ssh连…

MySQL中表的约束

1,概念 表中一定要有各种约束,通过约束,让我们来插入数据库中的数据是符合预期的。 约束本质是通过技术手段,倒逼程序员插入正确的数据;反过来,站在MySQL的角度来单,内部已经插进来的数据&…

即插即用hilo注意力机制,捕获低频高频特征

题目:Fast Vision Transformers with HiLo Attention 论文地址: https://arxiv.org/abs/2205.13213 创新点 HiLo自注意力机制:作者提出了一种新的自注意力机制,称为HiLo注意力,旨在同时捕捉图像中的高频和低频信息。该方法通过…

通信工程学习:什么是SPI串行外设接口

SPI:串行外设接口 SPI,即串行外设接口(Serial Peripheral Interface),是一种由Motorola公司首先在其MC68HCXX系列处理器上定义的同步串行接口技术。SPI接口主要用于微控制器(MCU)与外部设备之间…

1. 到底什么是架构

1. 什么是架构 定义:架构,又名软件架构,是有关软件整体结构与组件的抽象描述,用于指导大型软件系统各个方面的设计优秀架构的特点:优秀的性能、超强的TPS/QPS的承载能力、高可用决定了你能够支撑多少PV的流量 2. 什么…

【Linux修炼进程之权限篇】探讨Linux权限问题

【Linux修炼】——权限问题 目录 一:认识Linux下用户的分类 1.1:如何添加新用户【使用root用户创建添加】 1.2:su指令用法 二:Linux下权限是什么? 2.1:权限所认证的是身份(人身份角色) 2.2&#xff…

【WPF】04 Http消息处理类

这里引入微软官方提供的HttpClient类来实现我们的目的。 首先,介绍一下官方HttpClient类的内容。 HttpClient 类 定义 命名空间: System.Net.Http 程序集: System.Net.Http.dll Source: HttpClient.cs 提供一个类,用于从 URI 标识的资源发送 HTTP 请…

dbt doc 生成文档命令示例应用

DBT提供了强大的命令行工具,它使数据分析师和工程师能够更有效地转换仓库中的数据。dbt的一个关键特性是能够为数据模型生成文档,这就是dbt docs命令发挥作用的地方。本教程将指导您完成使用dbt生成和提供项目文档的过程。 dbt doc 命令 dbt docs命令有…

Gitxray:一款基于GitHub REST API的网络安全工具

关于Gitxray Gitxray是一款基于GitHub REST API的网络安全工具,支持利用公共 GitHub REST API 进行OSINT、信息安全取证和安全检测等任务。 Gitxray(Git X-Ray 的缩写)是一款多功能安全工具,专为 GitHub 存储库而设计。它可以用于…

STM32CUBEIDE的使用【三】RTC

于正点原子潘多拉开发板&#xff0c;使用stm32官方免费软件进行开发 CubeMx 配置 使用CubeMx 配置RTC 勾选RTC 设置日期和时间 配置LCD的引脚用来显示 STM32CUBEIDE 在usbd_cdc_if.c中重定向printf函数用于打印 #include <stdarg.h>void usb_printf(const char *f…

第十六章 RabbitMQ延迟消息之延迟插件优化

目录 一、引言 二、优化方案 三、核心代码实现 3.1. 生产者代码 3.2. 消息处理器 3.3. 自定义多延迟消息封装类 3.4. 订单实体类 3.5. 消费者代码 四、运行效果 一、引言 上一章节我们提到&#xff0c;直接使用延迟插件&#xff0c;创建一个延迟指定时间的消息&…

【C++算法】双指针

目录 一、快乐数&#xff1a; 二、有效三角形的个数&#xff1a; 三、盛最多水的容器&#xff1a; 四、复写0&#xff1a; 五、三数之和&#xff1a; 总结&#xff1a; 一、快乐数&#xff1a; 题目出处&#xff1a; 202. 快乐数 - 力扣&#xff08;LeetCode&#xff09…

ROS2 通信三大件之动作 -- Action

通信最后一个&#xff0c;也是不太容易理解的方式action&#xff0c;复杂且重要 1、创建action数据结构 创建工作空间和模块就不多说了 在模块 src/action_moudle/action/Counter.action 下创建文件 Counter.action int32 target # Goal: 目标 --- int32 current_value…

智能健康顾问:基于SpringBoot的系统

2相关技术 2.1 MYSQL数据库 MySQL是一个真正的多用户、多线程SQL数据库服务器。 是基于SQL的客户/服务器模式的关系数据库管理系统&#xff0c;它的有点有有功能强大、使用简单、管理方便、安全可靠性高、运行速度快、多线程、跨平台性、完全网络化、稳定性等&#xff0c;非常…

Qt:图片文字转base64程序

目录 一.Base64 1.编码原理 2.应用场景 3.优点 4.限制 5.变种 二.文字与Base64互转 1.ui设计 2.文字转Base64 3.Base64转文字 三.图片与Base64互转 1.ui设计 2.选择图片与图片路径 3.图片转Base64 4.Base64转图片 四.清空设置 五.效果 六.代码 base64conver…

PDF编辑不求人!4款高效工具,内容修改从此变得简单又快捷

咱们现在生活在一个数字时代&#xff0c;PDF文件可不就是工作、学习还有日常生活中经常要用的东西嘛。但遇到那些需要改动的PDF文件&#xff0c;是不是就觉得有点头疼啊&#xff1f; 因为传统的PDF文件真的不好编辑&#xff0c;这确实挺烦人的。不过呢&#xff0c;我今天要给你…

【北京迅为】《STM32MP157开发板嵌入式开发指南》- 第三十九章 Linux Misc驱动

iTOP-STM32MP157开发板采用ST推出的双核cortex-A7单核cortex-M4异构处理器&#xff0c;既可用Linux、又可以用于STM32单片机开发。开发板采用核心板底板结构&#xff0c;主频650M、1G内存、8G存储&#xff0c;核心板采用工业级板对板连接器&#xff0c;高可靠&#xff0c;牢固耐…