异步 CallBack 回顾

本文深入探讨异步编程中回调的使用方式,包括其在Node.js中读取文件的应用,以及如何解决回调地狱和错误处理难题。

前言

这篇文章主要是想回顾一下 CallBack 的一些使用方式

异步

所谓"异步",简单说就是一个任务分成几段,先执行第一段,然后转而执行其他任务,等做好了准备,再回过头执行第二段。这种不连续的执行,就叫异步。

前置知识

例子中异步代码主要使用 node 读取文件,复习一下 fs 模块的 fs.readFile 方法

const fs = require('fs')

fs.readFile('./a.txt', 'utf8', function(err, data) {
  if (err) return
  console.log(data)
})
复制代码

例子中 readFile 第三个参数接受一个方法,这个方法会等待文件异步读取完毕后执行并传入结果。

回调的问题

主要有两个问题,错误处理、回调地狱。接下来用代码演示

错误处理

try catch 只能捕获同步错误,对异步无能为力,比如

try {
  fs.readFile('./a.txt', 'utf8', function(err, data) {
    throw new Error('报错了!')
  })
} catch (err) {
  console.log('捕获错误:', err)
}
复制代码

因为 fs.readFile 是异步执行的,所以 fs.readFile 外的 try catch 不能捕获内部错误。接下来修改代码捕获错误

fs.readFile('./a.txt', 'utf8', function(err, data) {
  try {
    throw new Error('报错了!')
  } catch (err) {
    console.log('捕获错误:', err)
  }
})
复制代码

只需要把 try catch 放到内部就能捕获错误

回调地狱

比如有个需求,要求先读取a.txt的值,再读取b.txt,再读取c.txt等等等,这代码怎么写

fs.readFile('./a.txt', 'utf8', function(err, data) {
  fs.readFile('./b.txt', 'utf8', function(err, data) {
    fs.readFile('./c.txt', 'utf8', function(err, data) {
      // ....
    })
  })
})
复制代码

如果再加上try catch ...

fs.readFile('./a.txt', 'utf8', function(err, data) {
  try {
    fs.readFile('./b.txt', 'utf8', function(err, data) {
      try {
        fs.readFile('./c.txt', 'utf8', function(err, data) {
          try {
            // ....
          } catch (err) {
            console.log('捕获错误:', err)
          }
        })
      } catch (err) {
        console.log('捕获错误:', err)
      }
    })
  } catch (err) {
    console.log('捕获错误:', err)
  }
})
复制代码

这代码就看着想吐了。

逐步解决异步

现在有个需求,看下面代码,我想在 a.txtb.txtc.txt 都读取完毕后返回 collect 对象,这该怎么实现

const collect = {}

fs.readFile('./a.txt', 'utf8', function(err, data) {
  collect.a = data
})
fs.readFile('./b.txt', 'utf8', function(err, data) {
  collect.b = data
})
fs.readFile('./c.txt', 'utf8', function(err, data) {
  collect.c = data
})
复制代码

思考:能不能写一个方法,每次在 fs.readFile 执行完后查看 collect key 的数量

const collect = {}

// 哨兵函数
function out() {
  if (Object.keys(collect).length === 3) {
    console.log(collect)
  }
}

fs.readFile('./a.txt', 'utf8', function(err, data) {
  collect.a = data
  out()
})
fs.readFile('./b.txt', 'utf8', function(err, data) {
  collect.b = data
  out()
})
fs.readFile('./c.txt', 'utf8', function(err, data) {
  collect.c = data
  out()
})
复制代码

这个 out 方法就叫哨兵函数,但是这个方法用起来还是不方便。我想写一个 after 方法,接受次数和回调就能实现 out 方法比如

let out = after(3, function(data){
  console.log(data)
})
复制代码

接下来实现这个 after 方法

function after(times, callback) {
  return function() {
    if (--times === 0) {
      callback()
    }
  }
}

let out = after(3, function(data){
  console.log(data)
})

// 执行 out 3次
out()
out()
out() // console.log 执行
复制代码

after() 生成的 out 代替之前的 out 感觉舒服了好多。但是还是不够,如果我想在异步都执行完毕后执行多个方法怎么办

思考:用数组保存要执行都方法,当需要执行的时候依次执行

实现一个 Dep 对象,Dep 上有一个保存CallBackarr 数组、一个把CallBack添加到arron方法,一个哨兵函数emit,实现和用法如下

const collect = {}

const Dep = {
  arr: [],
  on(fn) {
    this.arr.push(fn)
  },
  emit() {
    if (Object.keys(collect).length === 3) {
      this.arr.forEach(function(fn){
        fn()
      })
    }
  }
}

Dep.on(function() {
  console.log(1)
})
Dep.on(function() {
  console.log(2)
})


fs.readFile('./a.txt', 'utf8', function(err, data) {
  collect.a = data
  Dep.emit()
})
fs.readFile('./b.txt', 'utf8', function(err, data) {
  collect.b = data
  Dep.emit()
})
fs.readFile('./c.txt', 'utf8', function(err, data) {
  collect.c = data
  Dep.emit()
})
复制代码

这就是特别简单的发布订阅,只是扩展一下思路

<think>首先,用户的问题是关于“异步循环”的信息,包括定义、使用方法、应用场景等。用户提供了几个引用,我需要参考这些引用,因为它们提供了相关背景。 引用内容摘要: - 引用[1]:讨论了Python的异步模型,基于asyncio框架,包括事件循环、async/await等。 - 引用[2]:关于Python中的异步编程及其应用场景,包括协程、事件循环在IO密集型任务中的应用。 - 引用[3]:解释了Python内置函数anext(),展示了异步for循环的使用示例。 - 引用[4]:关于循环队列的定义、应用场景和实现,但这是同步队列,可能与异步循环相关度较低。 用户指定要了解“异步循环”,所以我需要聚焦于异步编程中的循环概念,特别是在Python asyncio上下文中的异步循环(如事件循环和异步迭代)。 关键点: - **定义**:根据引用,异步循环通常指的是事件循环(Event Loop),它是异步编程的核心,负责调度和执行异步任务。在Python的asyncio模块中,事件循环处理协程的执行和IO操作。 - **使用方法**:包括如何创建事件循环、运行异步函数(使用asyncio.run)、使用async for实现异步迭代(如引用[3]中的示例)。 - **应用场景**:主要涉及高并发、IO密集型任务,如网络服务器、Web爬虫、数据库操作等(引用[2])。 用户最后的查询是:“请问异步循环的定义、使用方法及应用场景”。我需要结构化回答,确保清晰。 回答结构: 1. **定义**:解释什么是异步循环。 2. **使用方法**:描述如何在实际代码中使用,包括关键语法(如async for、事件循环管理)。 3. **应用场景**:列出常见应用领域。 4. **添加引用标识**:在回答中自然地引用来源,例如[^1]、[^2]等。用户提供了引用[1]、[^2]、[^3]、[^4],所以我应该对应使用它们。 引用映射: - 引用[1]:适用于Python异步模型概述。 - 引用[2]:适用于异步编程概念和应用场景。 - 引用[3]:适用于异步for循环的具体用法。 - 引用[4]:关于循环队列,但与异步循环直接相关度不高,可以适度忽略或提及在异步上下文中的应用。 在回答结尾,添加相关问题,这是系统指令的一部分:生成相关问题。 系统指令回顾: - 所有行内数学表达式用$...$,但这个问题没有数学内容,所以不需要。 - 独立公式用$$...$$,同样不适用。 - LaTeX语法正确,但这里可能不需要。 - 使用中文回答。 - 生成相关问题。 - 回答中引用的段落末尾添加引用标识。 回答结构: - 开头清晰介绍。 - 分点回答:定义、使用方法、应用场景。 - 结尾添加相关问题。 引用标识添加: - 例如,在提到Python asyncio时,添加[^1]。 - 在讨论应用场景时,引用[^2]。 - 在使用async for时,引用[^3]。 相关问题生成: 基于主题,生成与异步循环相关的问题,如优化、高级用法、与其他技术的比较等。 </think>### 异步循环的定义、使用方法及应用场景 异步循环是指在异步编程模型中,基于事件循环(Event Loop)机制来处理并发任务的一种循环结构。它允许程序在等待I/O操作(如网络请求、文件读写)时暂停当前任务,切换到其他任务,从而提高资源利用率和系统吞吐量。异步循环的核心是事件循环,它调度和执行异步任务(例如协程),而非传统的同步阻塞循环。在Python中,异步循环主要通过`asyncio`框架实现[^1][^2]。以下从定义、使用方法和应用场景三个方面详细介绍。 #### 1. 定义 异步循环的核心是事件循环,它是一个持续运行的机制,负责监听事件(如I/O完成、定时器触发)并执行对应的回调或协程。当程序遇到异步操作时,事件循环会将任务挂起,切换到其他可运行任务,然后在操作完成后恢复执行。这避免了传统同步循环中的阻塞问题,特别适合处理高并发场景[^1][^2]。关键特征包括: - **非阻塞性**:在等待I/O时,事件循环不会阻塞整个线程,而是执行其他任务。 - **基于协程**:使用`async`和`await`关键字定义异步函数,协程是事件循环管理的基本单元。 - **隐式调度**:开发者只需定义异步逻辑,事件循环自动处理任务调度和切换[^1][^2]。 在Python中,异步循环通常指事件循环本身或其上的异步迭代(如`async for`),后者用于遍历异步迭代器(例如从数据库查询流式数据)。这与同步循环(如`for`循环)不同,后者在I/O操作时会阻塞线程[^3]。 #### 2. 使用方法 异步循环的使用依赖于Python的`asyncio`框架(从Python 3.5开始支持)。以下是关键步骤和示例代码,展示如何创建事件循环、运行异步函数,以及使用异步迭代。 - **创建和运行事件循环**: - 使用`asyncio.run()`运行主异步函数,它是创建事件循环的简化方式(无需手动管理)。 - 示例代码:定义一个异步函数并运行事件循环。 ```python import asyncio async def main(): print("异步循环启动") # 模拟异步操作,等待1秒 await asyncio.sleep(1) print("异步操作完成") # 运行事件循环 asyncio.run(main()) ``` 此代码通过`asyncio.run()`自动创建事件循环,执行`main()`协程。在等待`sleep`时,事件循环可处理其他任务[^1][^2]。 - **使用异步迭代(`async for`)**: - 适用于遍历异步迭代器(如数据库查询结果流)。迭代器需实现`__aiter__`和`__anext__`方法,返回异步值。 - 内置函数`anext()`用于获取下一个异步值(类似同步的`next()`)。 - 示例代码:使用`async for`遍历异步生成器。 ```python import asyncio # 定义异步生成器 async def async_generator(): yield "数据1" yield "数据2" await asyncio.sleep(0.5) # 模拟I/O延迟 yield "数据3" raise StopAsyncIteration # 结束迭代 async def main(): async for item in async_generator(): print(item) # 输出: "数据1", "数据2", "数据3" asyncio.run(main()) ``` 在此示例中,`async for`与异步生成器结合,事件循环在每次`yield`时挂起任务,避免阻塞主线程。`anext()`在内部被隐式调用[^3]。 - **关键语法和注意事项**: - **async/await**:使用`async def`定义异步函数,`await`暂停当前协程(仅在异步函数内可用)。 - **事件循环管理**:高级场景可手动创建循环(如`loop = asyncio.get_event_loop()`),但推荐用`asyncio.run()`简化。 - **错误处理**:异步迭代使用`StopAsyncIteration`结束,类似同步的`StopIteration`。 - 优势:简化并发编程,避免回调地狱(Callback Hell),代码更可读[^1][^3]。 #### 3. 应用场景 异步循环主要用于I/O密集型和高并发场景,通过非阻塞方式提升性能。常见应用包括: - **网络编程**:如Web服务器、API客户端或爬虫,处理大量并发连接。事件循环能同时管理数千个网络请求,而不增加线程开销。例如,使用`aiohttp`库构建异步HTTP服务器时,异步循环高效处理请求-响应周期[^2]。 - **数据库操作**:在异步数据库驱动(如`asyncpg`)中,异步循环支持并发查询,减少等待数据库响应的延迟。适用于实时数据分析或高负载应用[^2]。 - **实时数据处理**:如消息队列消费(Kafka、RabbitMQ)或流数据处理。异步循环结合`async for`可实现实时数据摄取和转换,确保低延迟[^3]。 - **IO密集型任务**:文件读写、外部API调用等场景。异步循环允许任务在I/O等待期间切换,避免CPU闲置,显著提升吞吐量。例如,批量下载文件时,可并行发起多个请求[^1][^2]。 - **流水线缓冲区和线程池优化**:在网络或流水线系统中,异步循环可与循环队列(如环形队列)结合,作为缓冲区管理任务流,减少锁争用。[^4]。 总之,异步循环在Python中通过`asyncio`实现,提供高效、可扩展的并发方案。它的核心优势在于资源高效性和代码简洁性,特别适合现代Web应用、微服务和实时系统[^1][^2][^3]。 ### 相关问题 1. 如何优化异步循环的性能,避免常见陷阱(如事件循环阻塞)? 2. 异步循环与多线程/多进程有何区别?各自适用哪些场景? 3. 在异步编程中,如何处理错误和超时机制? 4. 有哪些库或工具(如aiohttp、asyncpg)可以扩展异步循环的功能? [^1]: Python 的异步模型从 Python 3.5 开始引入,基于 asyncio 框架: Python 的事件循环类似于 JavaScript,但需要显式创建和运行。通过 async 和 await 实现异步函数,简化了异步编程。 [^2]: Python中的异步编程及其应用场景。在Python中,异步编程是处理高并发和IO密集型任务的重要方式之一。本文介绍了Python中的异步编程概念、应用场景以及实际代码示例。 [^3]: Python内置函数anext()详解 基本用法 与异步for循环结合使用 与next()的区别 应用场景——《跟老吕学Python...。下面是一个使用异步for循环遍历异步迭代器的示例。 [^4]: 一篇文章讲清楚循环队列——循环队列是什么?有什么应用场景?怎么实现?。
评论
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符  | 博主筛选后可见
 
红包 添加红包
表情包 插入表情
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值